ebkalderon / tower-lsp

Language Server Protocol implementation written in Rust
Apache License 2.0
1.02k stars 57 forks source link

Separation of response from request in a custom method #339

Closed ratmice closed 2 years ago

ratmice commented 2 years ago

This in some ways seem similar to some of the things discussed in issue #231 where requests could be passed a cancellation token, My usage of tower-lsp is a little weird due to some !Send + !Sync variables, these variables have to live off in their own threads. The main Backend implementing LanguageClient keeps a channel to send these threads messages.

It then creates another thread which has a clone of Client which deals with receiving all the responses from all the threads and sending them back to the client. This works okay, until you need to deal with a request with a response, in implementing a custom_method() it can be difficult to obtain the value that needs to be returned from Backend itself.

Part of the reasoning behind the response thread is just that I don't see a nice way to receive from within Backend. e.g. to select! on both incoming jsonrpc and subjugate thread channels, since this is hidden within the serve function, But that would present an alternate way of responding, If backend had a good way of also receiving from channels, There wouldn't actually be any issue.

  1. Backend (Vec of mpsc::Sender one for each subjugate thread)
  2. N-subjugate threads (sync, each owns a !Send + !Sync value, mpsc::Receiver from backend, mpsc::Sender to Response thread)
  3. 1-Response task (async, has a clone of Client, mpsc::Receiver).

The communications topology in particular looks like: Client -> Backend -> Subjugate thread -> Response task -> Client

In #231 it was discussed that requests could be given a separate cancellation token as a part of the request. I was thinking perhaps you could do a similar thing, instead of returning from the custom method a value, a variation of custom_method which gets both the params and a ResponseToken, which it could pass to various responders.

P.S. sorry if this is a bit long & quite specific to the constraints upon which my implementation finds itself in

Edit: Making some clarifications about 'Response task', previously I had referred to this as 'Response thread', Edit 2: After thinking about it, perhaps my Backend can allocate a future, pass it through the communications channels, then when it arrives back in the async Response Task it becomes ready, and thus no actual modifications are needed in this case, because I may be able to make a ResponseToken without it actually being passed in from call.

ratmice commented 2 years ago

FWIW, I'm going to go ahead and close this, the way I got it working was basically described in Edit 2: using a tokio::sync::oneshot, One nice thing about this is that oneshot::Sender::send is sync so the subjugate threads can just respond directly to the Backend, so the Response task is kept out of the loop unless the subjugate thread needs to invoke the client directly.