-
Notifications
You must be signed in to change notification settings - Fork 121
Исправлена потенциальная гонка при закрытии документа в BSLTextDocumentService. #3724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
67104f4
6b92861
e2c80ca
fb345fb
ff642f4
f8abc95
9f00ce2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,6 +140,7 @@ | |
| public class BSLTextDocumentService implements TextDocumentService, ProtocolExtension { | ||
|
|
||
| private static final long AWAIT_CLOSE = 30; | ||
| private static final long AWAIT_FORCE_TERMINATION = 1; | ||
|
|
||
| private final ServerContext context; | ||
| private final LanguageServerConfiguration configuration; | ||
|
|
@@ -530,9 +531,34 @@ public void didClose(DidCloseTextDocumentParams params) { | |
| // Wait for all queued changes to complete (with timeout to avoid hanging) | ||
| if (!docExecutor.awaitTermination(AWAIT_CLOSE, TimeUnit.SECONDS)) { | ||
| docExecutor.shutdownNow(); | ||
| // Must wait for worker thread to finish even after shutdownNow, | ||
| // because finally block in worker may still be executing flushPendingChanges | ||
| boolean terminated = docExecutor.awaitTermination(AWAIT_FORCE_TERMINATION, TimeUnit.SECONDS); | ||
| if (!terminated) { | ||
| LOGGER.warn( | ||
| "Document executor for URI {} did not terminate within the additional timeout after shutdownNow()", | ||
| uri | ||
| ); | ||
| } | ||
| } | ||
| } catch (InterruptedException e) { | ||
| docExecutor.shutdownNow(); | ||
| // Wait briefly for worker to finish after interrupt | ||
| try { | ||
| boolean terminated = docExecutor.awaitTermination(AWAIT_FORCE_TERMINATION, TimeUnit.SECONDS); | ||
| if (!terminated) { | ||
| LOGGER.warn( | ||
| "Document executor for URI {} did not terminate within {} seconds after interrupt during document close", | ||
| uri, | ||
| AWAIT_FORCE_TERMINATION | ||
| ); | ||
| } | ||
| } catch (InterruptedException ignored) { | ||
| LOGGER.warn( | ||
| "Interrupted again while waiting for document executor for URI {} to terminate after shutdownNow", | ||
| uri | ||
| ); | ||
| } | ||
|
Comment on lines
+534
to
+561
|
||
| Thread.currentThread().interrupt(); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -201,6 +201,93 @@ void didClose() { | |
| textDocumentService.didClose(params); | ||
| } | ||
|
|
||
| @Test | ||
| void didCloseWithPendingChanges() throws IOException { | ||
| // given - open a document and make changes | ||
| var textDocumentItem = getTextDocumentItem(); | ||
| var didOpenParams = new DidOpenTextDocumentParams(textDocumentItem); | ||
| textDocumentService.didOpen(didOpenParams); | ||
|
|
||
| var documentContext = serverContext.getDocumentUnsafe(textDocumentItem.getUri()); | ||
| assertThat(documentContext).isNotNull(); | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isTrue(); | ||
|
|
||
| // when - submit multiple changes rapidly and then close immediately | ||
| var params = new DidChangeTextDocumentParams(); | ||
| var uri = textDocumentItem.getUri(); | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| params.setTextDocument(new VersionedTextDocumentIdentifier(uri, 2 + i)); | ||
| var range = Ranges.create(0, 0, 0, 0); | ||
| var changeEvent = new TextDocumentContentChangeEvent(range, "// Change " + i + "\n"); | ||
| List<TextDocumentContentChangeEvent> contentChanges = new ArrayList<>(); | ||
| contentChanges.add(changeEvent); | ||
| params.setContentChanges(contentChanges); | ||
| textDocumentService.didChange(params); | ||
| } | ||
|
|
||
| // then - close should wait for pending changes to complete | ||
| var closeParams = new DidCloseTextDocumentParams(); | ||
| closeParams.setTextDocument(new TextDocumentIdentifier(uri)); | ||
| textDocumentService.didClose(closeParams); | ||
|
|
||
| // verify the document is closed | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isFalse(); | ||
| } | ||
|
Comment on lines
+204
to
+236
|
||
|
|
||
| @Test | ||
| void didCloseDuringActiveChange() throws IOException { | ||
| // given - open a document | ||
| var textDocumentItem = getTextDocumentItem(); | ||
| var didOpenParams = new DidOpenTextDocumentParams(textDocumentItem); | ||
| textDocumentService.didOpen(didOpenParams); | ||
|
|
||
| var documentContext = serverContext.getDocumentUnsafe(textDocumentItem.getUri()); | ||
| assertThat(documentContext).isNotNull(); | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isTrue(); | ||
|
|
||
| // when - submit a change | ||
| var params = new DidChangeTextDocumentParams(); | ||
| var uri = textDocumentItem.getUri(); | ||
| params.setTextDocument(new VersionedTextDocumentIdentifier(uri, 2)); | ||
| var range = Ranges.create(0, 0, 0, 0); | ||
| var changeEvent = new TextDocumentContentChangeEvent(range, "// New content\n"); | ||
| List<TextDocumentContentChangeEvent> contentChanges = new ArrayList<>(); | ||
| contentChanges.add(changeEvent); | ||
| params.setContentChanges(contentChanges); | ||
| textDocumentService.didChange(params); | ||
|
|
||
| // then - close immediately while change may still be processing | ||
| var closeParams = new DidCloseTextDocumentParams(); | ||
| closeParams.setTextDocument(new TextDocumentIdentifier(uri)); | ||
| textDocumentService.didClose(closeParams); | ||
|
|
||
| // verify the document is closed | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isFalse(); | ||
| } | ||
|
Comment on lines
+238
to
+267
|
||
|
|
||
| @Test | ||
| void didCloseAwaitTerminationCompletes() throws IOException { | ||
| // given - open a document | ||
| var textDocumentItem = getTextDocumentItem(); | ||
| var didOpenParams = new DidOpenTextDocumentParams(textDocumentItem); | ||
| textDocumentService.didOpen(didOpenParams); | ||
|
|
||
| var uri = textDocumentItem.getUri(); | ||
| var documentContext = serverContext.getDocumentUnsafe(uri); | ||
| assertThat(documentContext).isNotNull(); | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isTrue(); | ||
|
|
||
| // when - close the document (which should wait for executor to terminate) | ||
| var closeParams = new DidCloseTextDocumentParams(); | ||
| closeParams.setTextDocument(new TextDocumentIdentifier(uri)); | ||
| textDocumentService.didClose(closeParams); | ||
|
|
||
| // then - verify the document is properly closed | ||
| // The close should complete successfully even if executor needs time to terminate | ||
| assertThat(serverContext.isDocumentOpened(documentContext)).isFalse(); | ||
| } | ||
|
Comment on lines
+269
to
+289
|
||
|
|
||
| @Test | ||
| void didClosePublishesEmptyDiagnosticsWhenClientDoesNotSupportPullDiagnostics() throws IOException { | ||
| // given - open a document | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable 'ignored' is declared but never used in the catch block. While this is a common pattern for documenting intentional suppression of exceptions, the variable should actually be named with an underscore prefix (e.g., '_') or simply omit the variable name entirely in modern Java, as the catch block already has explanatory logging that makes the intention clear.