{"record":{"id":"be678b2da9dd2335","repo":"alibaba/spring-ai-alibaba","slug":"error-be678b","errorCode":null,"errorMessage":"实验 {} 状态为 {}，无法停止","messagePattern":"实验 (.+?) 状态为 (.+?)，无法停止","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-start/src/main/java/com/alibaba/cloud/ai/studio/admin/service/impl/ExperimentServiceImpl.java","lineNumber":279,"sourceCode":"        log.info(\"停止实验: {}\", id);\n        \n        if (id == null) {\n            throw new IllegalArgumentException(\"Experiment ID cannot be null\");\n        }\n        \n        // 获取实验信息\n        ExperimentDO experimentDO = experimentMapper.selectById(id);\n        if (experimentDO == null) {\n            throw new IllegalArgumentException(\"Experiment not found: \" + id);\n        }\n\n\n\n        // 检查实验状态\n        if (ExperimentStatus.COMPLETED.getCode().equals(experimentDO.getStatus()) ||\n            ExperimentStatus.FAILED.getCode().equals(experimentDO.getStatus()) ||\n            ExperimentStatus.STOPPED.getCode().equals(experimentDO.getStatus())) {\n            log.warn(\"实验 {} 状态为 {}，无法停止\", id, experimentDO.getStatus());\n            return Experiment.fromDO(experimentDO);\n        }\n\n\n        \n        // 更新实验状态为已停止\n        experimentDO.setStatus(String.valueOf(ExperimentStatus.STOPPED));\n        experimentDO.setUpdateTime(LocalDateTime.now());\n        \n        int result = experimentMapper.updateById(experimentDO);\n        if (result <= 0) {\n            throw new RuntimeException(\"Failed to stop experiment\");\n        }\n        \n        log.info(\"实验停止成功: {}\", id);\n        return Experiment.fromDO(experimentDO);\n    }\n","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-start/src/main/java/com/alibaba/cloud/ai/studio/admin/service/impl/ExperimentServiceImpl.java#L261-L297","documentation":"ExperimentServiceImpl.stop refuses to stop an experiment that is already in a terminal state — COMPLETED, FAILED, or STOPPED. It logs this warning (including the ID and current status) and returns the unchanged experiment instead of throwing. Stopping is only meaningful for experiments still running or pending, since terminal states cannot transition to STOPPED.","triggerScenarios":"Calling experimentService.stop(id) on an experiment whose status is COMPLETED, FAILED, or STOPPED — e.g., a double-click on the stop button, or a client caching state and issuing a redundant stop after the experiment finished.","commonSituations":"Race between UI polling and user pressing stop; retry logic replaying a stop request after the experiment already failed/completed; scheduled job attempting cleanup of already-finished experiments.","solutions":["Check experiment status before calling stop; only issue stop when status is RUNNING/PENDING.","Treat the returned experiment's terminal status as success (idempotent handling) rather than an error.","Debounce/disable the stop button in the UI once the status poller reports a terminal state.","If a stale status caused this, refresh the experiment via getById and re-evaluate."],"exampleFix":"// before\nExperiment exp = experimentService.getById(id);\nexperimentService.stop(id); // warning: already terminal\n\n// after\nExperiment exp = experimentService.getById(id);\nif (!ExperimentStatus.COMPLETED.getCode().equals(exp.getStatus())\n        && !ExperimentStatus.FAILED.getCode().equals(exp.getStatus())\n        && !ExperimentStatus.STOPPED.getCode().equals(exp.getStatus())) {\n    experimentService.stop(id);\n}","handlingStrategy":"validation","validationCode":"Set<String> terminal = Set.of(\n    ExperimentStatus.COMPLETED.getCode(),\n    ExperimentStatus.FAILED.getCode(),\n    ExperimentStatus.STOPPED.getCode());\nExperiment exp = experimentService.getById(id);\nif (exp != null && terminal.contains(exp.getStatus())) {\n    return; // already terminal, nothing to stop\n}\nexperimentService.stop(id);","typeGuard":null,"tryCatchPattern":"Experiment result = experimentService.stop(id);\nif (terminalStatuses.contains(result.getStatus())) {\n    log.info(\"Experiment {} already terminal ({}), stop was a no-op\", id, result.getStatus());\n}","preventionTips":["Only issue stop for RUNNING/PENDING experiments; check status first.","Make stop idempotent in client code — treat terminal-state returns as success.","Debounce stop buttons and disable them once polling shows a terminal state.","Use optimistic locking or a status guard when transitioning experiment state."],"tags":["java","spring","state-machine","experiment","idempotency"],"backgroundTag":"invalid-state-transition","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}