Skip to content
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

LanguageServerWrapper is not stopped on Eclipse shutdown #681

Open
basilevs opened this issue Jun 4, 2023 · 7 comments
Open

LanguageServerWrapper is not stopped on Eclipse shutdown #681

basilevs opened this issue Jun 4, 2023 · 7 comments

Comments

@basilevs
Copy link
Contributor

basilevs commented Jun 4, 2023

Eclipse outputs the following error to standard error on shutdown, if any LSP-enabled editors are still opened:

Jun 04, 2023 8:12:12 PM org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer fireStreamClosed
INFO: The input stream was closed.
java.io.InterruptedIOException
	at java.base/java.io.PipedInputStream.read(PipedInputStream.java:328)
	at org.vgcpge.eclipse.copilot.ui.internal.OrphanPipedInputStream.read(OrphanPipedInputStream.java:21)
	at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen(StreamMessageProducer.java:79)
	at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run(ConcurrentMessageProcessor.java:113)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

The problem is caused by interruption of the MessageProducer on shutdown.

Thread [Framework stop - Equinox Container: e7e68fc9-1058-4290-853a-605fd888929e] (Suspended (breakpoint at line 670 in ThreadPoolExecutor$Worker))	
	owns: Object  (id=178)	
	ThreadPoolExecutor$Worker.interruptIfStarted() line: 670	
	ThreadPoolExecutor.interruptWorkers() line: 769	
	ThreadPoolExecutor.shutdownNow() line: 1417	
	LanguageServerWrapper.stopDispatcher() line: 210	
	0x00000008013a7440.accept(Object) line: not available	
	CopyOnWriteArrayList<E>.forEach(Consumer<? super E>) line: 807	
	CopyOnWriteArraySet<E>.forEach(Consumer<? super E>) line: 425	
	LanguageServiceAccessor.shutdownAllDispatchers() line: 686	
	LanguageServerPlugin.stop(BundleContext) line: 43	
	BundleContextImpl$3.run() line: 875	
	BundleContextImpl$3.run() line: 1	
	AccessController.executePrivileged(PrivilegedExceptionAction<T>, AccessControlContext, Class<?>) line: 807	
	AccessController.doPrivileged(PrivilegedExceptionAction<T>) line: 569	
	BundleContextImpl.stop() line: 867	
	EquinoxBundle.stopWorker0() line: 1046	
	EquinoxBundle$EquinoxModule.stopWorker() line: 376	
	EquinoxBundle$EquinoxModule(Module).doStop() line: 660	
	EquinoxBundle$EquinoxModule(Module).stop(Module$StopOptions...) line: 521	
	ModuleContainer$ContainerStartLevel.decStartLevel(int, List<Module>) line: 1893	
	ModuleContainer$ContainerStartLevel.doContainerStartLevel(Module, int, FrameworkListener...) line: 1768	
	EquinoxBundle$SystemBundle$EquinoxSystemModule(SystemModule).stopWorker() line: 275	
	EquinoxBundle$SystemBundle$EquinoxSystemModule.stopWorker() line: 208	
	EquinoxBundle$SystemBundle$EquinoxSystemModule(Module).doStop() line: 660	
	EquinoxBundle$SystemBundle$EquinoxSystemModule(Module).stop(Module$StopOptions...) line: 521	
	EquinoxBundle$SystemBundle$EquinoxSystemModule(SystemModule).stop(Module$StopOptions...) line: 207	
	EquinoxBundle$SystemBundle$EquinoxSystemModule$1.run() line: 226	
	Thread.run() line: 833	

Investigation shows, that LSP4E relies on LanguageServerWrapper to stop processing before its Executor is shutdown. However, the only way to stop processing is to call LanguageServerWrapper.stop() and that is not called on shutdown - the call is delayed too much by org.eclipse.lsp4e.LanguageServerWrapper.startStopTimer().

AFAIK, this results in forceful termination of child process without a chance for graceful shutdown.

@basilevs
Copy link
Contributor Author

basilevs commented Jun 4, 2023

Why does org.eclipse.lsp4e.LanguageServerPlugin.stop(BundleContext) use org.eclipse.lsp4e.LanguageServiceAccessor.shutdownAllDispatchers() and not org.eclipse.lsp4e.LanguageServiceAccessor.clearStartedServers()? It would not solve the problem, as org.eclipse.lsp4e.LanguageServerWrapper.stop() is asynchronous in nature, but would be a good first step.

@mickaelistria
Copy link
Contributor

@basilevs your proposal sounds good. Can you please submit a PR about that?

@rubenporras
Copy link
Contributor

Looks good to me as well. @mickaelistria , would we then remove shutdownAllDispatchers? As far as I can tell, the method would not be used any longer.

@basilevs
Copy link
Contributor Author

basilevs commented Jun 5, 2023

I no longer believe that a simple change will help. Stop process needs to be monitored and completed before shutdown of the executors.

The new suggested solution is:

  • make stop synchronous
  • compensate for this in org.eclipse.lsp4e.LanguageServerWrapper.start() with an asyncExec and additional promise indirection to ensure start() is still non-blocking
  • call stop from org.eclipse.lsp4e.LanguageServerWrapper.stopDispatcher() before shutting down Executors

I will work on a PR, but ETA is unclear.

UPDATE:
This approach has failed due to a requirement for stop() to quickly update part of LanguageServerWrapper state. A new approach is described and implemented in #688.

@basilevs
Copy link
Contributor Author

basilevs commented Jun 6, 2023

org.eclipse.lsp4e.LanguageServerWrapper.start() fails to properly manage mutexes - the method in synchronized, but accesses class internals from a background thread without mutex protections.

basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
Shutdown should not immediately terminate communication.

Test eclipse#681
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
stop() is now synchronous
start() calls stop and should not block UI.
We move the call to stop to a backgroud thread and restore missing
synchronization.
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
Shutdown should not immediately terminate communication.
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
stop() is now synchronous
start() calls stop and should not block UI.
We move the call to stop to a backgroud thread and restore missing
synchronization.
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 6, 2023
Asynchronous nature of stop() helps with quick reaction to closed
editors. An attempt to make it asynchronous confused isActive() check.
This change reverts the attempt, and allows for monitoring of stopping
processing via a returned Future.

Note the use of Plafotm.getLog() - ILog can't be received from plugin
activator during platform shutdown.
@basilevs
Copy link
Contributor Author

basilevs commented Jun 6, 2023

Well, this did not work out - the quick acting stop is required during disconnect, so I had to revert synchronous stop.

basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 7, 2023
The cleanup on shutdown is special - it intentionally does not remove
wrapper registration to prevent reinitialization of wrappers by client
code.
This way, situation when a wrapper is active after plugin stop is
prevented and orderly shutdown is possible.
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 7, 2023
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 7, 2023
Note the use of Plafotm.getLog() - ILog can't be received from plug-in
activator during platform shutdown.
basilevs added a commit to basilevs/lsp4e that referenced this issue Jun 7, 2023
@basilevs
Copy link
Contributor Author

basilevs commented Jun 7, 2023

#688 is ready for review and seems to fix the described problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants