{"record":{"id":"aa8579d637173681","repo":"alibaba/spring-ai-alibaba","slug":"thread-is-not-active-or-already-released-threadn","errorCode":null,"errorMessage":"Thread is not active or already released: <threadName>","messagePattern":"Thread is not active or already released: <threadName>","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mongo/MongoSaver.java","lineNumber":490,"sourceCode":"\t\t\t\tthrow new IllegalStateException(\"Thread not found: \" + threadName);\n\t\t\t}\n\n\t\t\t// Mark thread as released atomically\n\t\t\t// Use findOneAndUpdate with condition to ensure we only release active threads\n\t\t\tDocument releaseFilter = new Document(\"_id\", metaId)\n\t\t\t\t\t.append(FIELD_IS_RELEASED, false); // Only release if not already released\n\n\t\t\tDocument updatedDoc = threadMetaCollection.findOneAndUpdate(\n\t\t\t\t\tclientSession,\n\t\t\t\t\treleaseFilter,\n\t\t\t\t\tUpdates.set(FIELD_IS_RELEASED, true),\n\t\t\t\t\tnew FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)\n\t\t\t);\n\n\t\t\tif (updatedDoc == null) {\n\t\t\t\t// Thread was already released or doesn't exist\n\t\t\t\tclientSession.abortTransaction();\n\t\t\t\tthrow new IllegalStateException(\"Thread is not active or already released: \" + threadName);\n\t\t\t}\n\n\t\t\t// Get checkpoints for Tag (using thread_id)\n\t\t\tMongoCollection<Document> checkpointCollection = database.getCollection(CHECKPOINT_COLLECTION);\n\t\t\tString checkpointDocId = CHECKPOINT_PREFIX + threadId;\n\t\t\tDocument checkpointDoc = checkpointCollection.find(clientSession, new BasicDBObject(\"_id\", checkpointDocId))\n\t\t\t\t\t.first();\n\n\t\t\tCollection<Checkpoint> checkpoints = Collections.emptyList();\n\t\t\tif (checkpointDoc != null) {\n\t\t\t\tString checkpointsStr = checkpointDoc.getString(DOCUMENT_CONTENT_KEY);\n\t\t\t\tif (checkpointsStr != null) {\n\t\t\t\t\tcheckpoints = deserializeCheckpoints(checkpointsStr);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclientSession.commitTransaction();\n\t\t\treturn new Tag(threadName, checkpoints);","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mongo/MongoSaver.java#L472-L508","documentation":"During release(), MongoSaver marks the thread as released with a conditional findOneAndUpdate filtered on _id = metaId AND is_released = false. If the update matches nothing (thread already released, or meta vanished between the read and the update), the transaction is aborted and IllegalStateException('Thread is not active or already released: <threadName>') is thrown.","triggerScenarios":"Calling release(config) twice on the same thread; concurrent release calls racing on the same threadId; a thread that was released in a previous run but is still referenced by application state.","commonSituations":"Retry logic re-invoking release after a timeout even though the first attempt committed; multiple workers cleaning up the same thread; idempotency assumptions in batch shutdown code.","solutions":["Treat release as non-idempotent: track released threads in application state and skip already-released ones","Catch IllegalStateException and, if the message says 'already released', treat it as success in cleanup paths","Serialize releases per thread (locking or a leader election) if multiple workers may release concurrently"],"exampleFix":"// before\nsaver.release(cfg); // second call throws\n// after\ntry {\n    saver.release(cfg);\n} catch (IllegalStateException e) {\n    if (!e.getMessage().contains(\"already released\")) throw e;\n    // already released: treat as success\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    saver.release(cfg);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"already released\")) {\n        return; // idempotent success\n    }\n    throw e;\n}","preventionTips":["Treat release as one-shot; track released threads in app state","Avoid releasing the same threadId from multiple workers concurrently","Design cleanup jobs to tolerate already-released threads"],"tags":["mongo","thread-not-found","idempotency","concurrency"],"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"}