{"record":{"id":"3b3af61db26c5ce4","repo":"spring-projects/spring-ai","slug":"skipping-add-for-session-sessionid-index-was","errorCode":null,"errorMessage":"Skipping add for session '${sessionId}': index was concurrently cleared","messagePattern":"Skipping add for session '(.+?)': index was concurrently cleared","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java","lineNumber":189,"sourceCode":"\t * {@link #clearIndex(String)} is called concurrently for the same session from a\n\t * different thread. The advisor's built-in flow prevents this via the atomic\n\t * fingerprint-check in {@code initializeSession()}. Direct callers must ensure that\n\t * no concurrent {@code clearIndex()} runs for the same session; if one does, the\n\t * document is silently dropped (the session was cleared anyway).\n\t * @param sessionId the session ID associated with the tool\n\t * @param id unique identifier for the tool\n\t * @param toolName name of the tool\n\t * @param toolDescription description of the tool (searchable)\n\t */\n\tpublic void add(String sessionId, String id, String toolName, String toolDescription) {\n\t\ttry {\n\t\t\tSessionIndex sessionIndex = getOrCreateSessionIndex(sessionId);\n\t\t\tDocument doc = this.createDocument(sessionId, id, toolName, toolDescription);\n\t\t\tsessionIndex.writer.addDocument(doc);\n\t\t}\n\t\tcatch (AlreadyClosedException ex) {\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"Skipping add for session '\" + sessionId + \"': index was concurrently cleared\");\n\t\t\t}\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to add document to index\", e);\n\t\t}\n\t}\n\n\t/**\n\t * Commits all pending changes to all session indexes. Call this after batch additions\n\t * for better performance.\n\t */\n\tpublic void commit() {\n\t\tfor (Map.Entry<String, SessionIndex> entry : this.sessionIndexes.entrySet()) {\n\t\t\ttry {\n\t\t\t\tSessionIndex sessionIndex = entry.getValue();\n\t\t\t\tsessionIndex.writer.commit();\n\t\t\t\tsessionIndex.refreshReader();\n\t\t\t}","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java#L171-L207","documentation":"During LuceneToolIndex.add, if the per-session index writer was closed concurrently (e.g. by a clear() call on another thread), Lucene throws AlreadyClosedException. The index logs this warning and skips the add instead of failing, because the session was intentionally discarded. It reflects a benign race between indexing and clearing.","triggerScenarios":"Calling indexTool/indexTools (which call add) for a sessionId at nearly the same moment another thread calls clear() on that session, closing the writer; retry/replay of indexing after session cleanup.","commonSituations":"Concurrent tool registration while an HTTP session or agent session is torn down; scheduled indexing jobs racing with user-initiated session clears; multiplexed requests sharing a sessionId whose lifecycle is managed elsewhere.","solutions":["Treat the warning as benign: the session was cleared, so the skipped document is intentionally discarded.","Synchronize session lifecycle: clear() the session only after all pending indexTool calls complete for that sessionId.","Check ordering in your code — don't index tools into a session you are simultaneously clearing.","If documents are needed post-clear, re-add them after the clear completes; a new SessionIndex will be created lazily."],"exampleFix":"// before: racing clear and add\nexecutor.submit(() -> index.indexTools(sessionId, tools));\nindex.clear(sessionId); // may close writer mid-add\n// after: clear after indexing completes\nFuture<?> f = executor.submit(() -> index.indexTools(sessionId, tools));\nf.get();\nindex.clear(sessionId);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// AlreadyClosedException inside add is swallowed by the library with a warning;\n// coordinate lifecycle at call site:\nsynchronized (sessionLock) {\n    index.indexTools(sessionId, tools);\n}","preventionTips":["Do not call clear(sessionId) concurrently with indexTool calls for the same session.","Complete indexing before session teardown.","Centralize session lifecycle management in one component.","Treat this warning as expected noise when clearing sessions is routine."],"tags":["tool-search","lucene","concurrency","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}