Skip to content

Handle LSP Initialize request synchronously #1750

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

Closed
wants to merge 1 commit into from

Conversation

pepeiborra
Copy link
Collaborator

The LSP spec. has strong restrictions around what messages are allowed
in between the Initialize request and response. Handling the message
synchronously goes a long way towards satisfying those invariants.

The LSP spec. has strong restrictions around what messages are allowed
in between the Initialize request and response. Handling the message
synchronously goes a long way towards satisfying those invariants.
@@ -43,7 +44,12 @@ requestHandler m k = LSP.requestHandler m $ \RequestMessage{_method,_id,_params}
trace x = otTracedHandler "Request" (show _method) $ \sp -> do
traceWithSpan sp _params
x
writeChan chan $ ReactorRequest (SomeLspId _id) (trace $ LSP.runLspT env $ resp' =<< k ide _params) (LSP.runLspT env . resp' . Left)
item = case _method of
SInitialize ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dead code - handleInit is the actual initialize request handler. lsp will never actually call a InitializeRequest handler defined the regular way since initialize is special. Instead it defines its own handler, into which custom logic can be inserted using doInitialize

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case it sounds like InitializeRequest is already being handled synchronously, is that right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

I've also been confused by the out of order progress messages and something non-obvious is going on. It might even be a bug in lsp-test.

I did try to investigate the ghcide code path and I couldn't figure out how it was possible for us to send progress messages before the initialize response.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second part seems easy to explain: we call shakeOpen inside the doInitialize handler, shakeOpen will evaluate the rules and call kick, and kick may trigger progress reporting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I thought shakeOpen was only initializing state.

@ndmitchell
Copy link
Collaborator

Will this cause progress begin/end messages to be generated, but they won't make it back to the client until after the initialise response returns? If they arrive later, what will happen? What about messages which are allowed during initialise, like logging - are they going to be delayed too?

@pepeiborra
Copy link
Collaborator Author

Will this cause progress begin/end messages to be generated, but they won't make it back to the client until after the initialise response returns? If they arrive later, what will happen? What about messages which are allowed during initialise, like logging - are they going to be delayed too?

Turns out this change won't make any difference, since the Initialize request is already being handled synchronously.

It's not obvious to me how to block progress reporting during initialise, since we call shakeOpen inside the initialise handler. I think this might require a change in the lsp package to allow doInitialize to return an IO action to run after sending the response. @wz1000 what do you think?

@pepeiborra pepeiborra closed this Apr 18, 2021
@pepeiborra pepeiborra deleted the initialise-sync branch April 18, 2021 13:21
@ndmitchell
Copy link
Collaborator

We already have a flag to say we want testing messages. How about making that a mode. ProgressAlways (testing), ProgressSometimes (normal), ProgressNever (init).

@wz1000
Copy link
Collaborator

wz1000 commented Apr 18, 2021

doInitialize to return an IO action to run after sending the response

Maybe that could work, but I'm not sure how all the data dependencies will work out. We already use mdo in both shakeOpen and lsps initialize handler, and there is that trick with unsafeInterleaveIO readMVar in handleInit, so it is already a bit of a circular tangled mess.

We need the result of doInitialize to construct the monadic environment all the other handlers will be running in.

The only way to find out is by prototyping the change both in lsp and ghcide.

@wz1000
Copy link
Collaborator

wz1000 commented Apr 18, 2021

Usually we would use the initialized notification as a trigger to execute actions after initialization, but I guess that can't work right now because we need the result of shakeOpen for the environment the notification handler will be running in.

@pepeiborra
Copy link
Collaborator Author

doInitialize to return an IO action to run after sending the response

Maybe that could work, but I'm not sure how all the data dependencies will work out. We already use mdo in both shakeOpen and lsps initialize handler, and there is that trick with unsafeInterleaveIO readMVar in handleInit, so it is already a bit of a circular tangled mess.

We need the result of doInitialize to construct the monadic environment all the other handlers will be running in.

The only way to find out is by prototyping the change both in lsp and ghcide.

The easiest thing would be to change doInitialize to just send the response immediately and then evaluate the handler.

@ndmitchell
Copy link
Collaborator

My understanding is the flow is initialize request/response, where we are super limited. Then we get initialized. Why not move all our initialisation to initialized, and just stick with the minimum reporting of capabilities back? Then we wouldn't call the kick or anything.

Even if we did do a shakeOpen, I don't see why we need to do a shakeRun inside it, but I'm super unclear about the flow there.

@pepeiborra
Copy link
Collaborator Author

Right, we could move some of the initialization to the Initialized notification handler.

shakeOpen sets up the ShakeExtras environment which includes the ShakeSession, but we could delay creating the session until the first LSP file event.

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

Successfully merging this pull request may close these issues.

3 participants