-
-
Notifications
You must be signed in to change notification settings - Fork 17
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
Always run server handlers inside an async
context
#24
Conversation
Amazing 🤩 |
This is a great catch, and I think may be the root cause behind some uncaught exceptions that have been tearing down FSAC. |
hmm, there is one particular reason why I ran the handler outside the note the text saying: I realise, I might have missed something but I believe this PR breaks serialization requirement as now please correct me if I am wrong here |
what i am doing with this in csharp-ls is that I am registering the workload into internal queue before returning an async block in the handler when request semantics require sequencing (like textDocumentDidChange) and ensure that is run in proper sequence -- otherwise we break sequencing "commandments" from LSP, like "get definitions request should consider the results of previous did change request in-order", like detailed here: obviously this part of the LSP spec increases the complexity of the LSP server (in favor of the ability to run some parts of the handler in parallel to each other and thus increase performance), but I don't see an easy way around that.. |
my suggestion would be to revert this change and rather do this (pseudo code): let mutable asyncLspResult
try
asyncLspResult <- run param
with Exception error:
let rpcException = LocalRpcException(ex.Message)
rpcException.ErrorCode <- xxxx // ???
raise rpcException
(...) needs to be tested though.. |
Thanks for the extra context @razzmatazz! This indeed broke the ordering, so, yes, it's better to revert it and take another good look. I wonder if ordering could be fixed properly by tweaking the synchronisation context of JSON RPC though... |
Oh definitely, if you have better insights/solutions than me -- then lets do that. The best approach would be to provide a higher-level abstraction over LSP in Ionide.LSP with automatic support for synchronization/queuing that would be done automatically for the user of the library by taking into account LSP semantics but it is over my head for now :) |
I played a bit more with the original version (= revert of this commit). A tactically inserted Async.Sleep(100) was actually enough to mess up text sync. So, I'm curious @razzmatazz @baronfel do you actually do the fully concurrent request/notification processing in your servers OR your actually properly sequence message processing yourself effectively doing a single threader request/response? |
last time I checked fsac does not support incremental text sync, always asks for a "full" document sync from the client thus even though it does not break the document content it may have sequencing problems when serializing request order.. but i think this problem was there present in fsac before conversion to streamjsonrpc in ionide.lsp; csharp-ls I have 'incremental' sync enabled thus I really need to have request ordering working right as the user may be typing fast :) as for what i do in csharp-ls is this: in particular i have this |
from my point of view LSP semantics for serialization could be pushed up into Ionide.LSP but not sure what is the right abstraction model |
Yeah, that's a tricky one. My current feeling is that doing proper concurrent/parallel processing is really hard in LSP, because although you can reorder certain things, a lot more things need to be processed strictly in-order. I feel that serializing message processing in Ionide.LSP can probably simplify things quite a bit. The server, of course, can still do whatever background processing it should to try to service requests as fast as it can (maybe using somewhat stale data). But the inverse will just push all the synchronization complexity to the actual server implementers. I guess another benefit of fully synced message processing is that it'd be easy to add basic support for request cancellation at the library level: the server gets @razzmatazz do you see any potential downsides for your use-case from switching to fully sequential message processing? |
there is already support for cancellation in Ionide.LSP, see:
everything plugs in rather nicely with F#'s ambient cancellation mechanism; this was one of the main points of migrating to StreamJsonRpc i can confirm that cancellation works with charp-ls and most probably does so automatically in fsac too |
as for forcing serialization on ionide.lsp (library) level in a primitive way, I would be against it when testing with csharp-ls, the performance and experience drops a lot because of not being able to process textdocumentdidchange while editor is waiting for response to e.g. previous "code lens" request that has not finished yet |
To releave of this complexity I would rather prefer to introduce some kind of mechanism/model in ionide.lsp that does it for server implementers that takes into accoujnt LSP semantics and provides hooks to handler methods to signal when the next request can proceed or if the handler wants to block server state until the request finishes and subsequent requests should wait. |
Do you mean that it's "straightforward" to add support or that it's already somehow hooked in via cancellation token? I can't seem to find anything around handling of "$/cancelRequest".
@razzmatazz ok, that's fair. I'll do some more experimentation in my server adding the necessary synchronization points. Perhaps something more general will come out of this that we could discuss further. |
mhm.. it should work very easily, i.e. if you interact with c# libraries that accept cancellationtoken then do let! ct = Async.CancellationToken
(.. use ct in your handler code ...) there is an example here: ...where I acquire ambient cancellationtoken and pass it to roslyn f# does automatic things with ambient cancellationtoken that I myself don't really understand but it is nice that I don't have to pass it around anymore :) |
The StreamJsonRpc library looks at the shape of the message handler you've configured, and if a Regarding the synchronization, I'm conflicted.
So I think what we should do is:
What do you two think? |
thats sounds exactly the way I think we should go |
👍 |
The explanation is in the comment in the code.
In my LSP server the problem manifested itself as a server hanging during initialisation. Turned out some of the code outside of an
async
context raised an exception which didn't kill the process but turned the server into a potato. At the same time the client kept waiting for a response but never got one.