[#22] Threat shutdown and exit requests correctly

Problem:
As in [#22], we want to shut down our LSP server correctly.
That means we should close the pipe and end process after an exit request.
This require proper `shutdown : unit -> bool` function passed
to `Jsonrpc2.Make.run` which returns `true` after
the server received an exit request.

Right now both exit and shutdown requests are setting
LSP server's internal var `_quit` to true. It's very intuitive to
use this var in `shutdown`, but we can't do it, since in this case
the server would stop receiving new messages right after the
shutdown request, despite the fact  according to the LSP specification
it had to wait the exit request.

Solution:
Instead of `_quit : bool` use
```
status : [ `Running | `ReceivedShutdown | `ReceivedExit ]
```
and suggest
```
shutdown () = s#get_status = `ReceivedExit
```
in docs for `Jsonrpc2.Make.run` and in the templete/example.
This commit is contained in:
Anton Sorokin 2023-08-04 15:27:12 +03:00 committed by Simon Cruanes
parent c6969ab87c
commit 09311ae258
3 changed files with 17 additions and 5 deletions

View file

@ -81,7 +81,10 @@ class lsp_server =
let run () =
let s = new lsp_server in
let server = Linol_lwt.Jsonrpc2.create_stdio s in
let task = Linol_lwt.Jsonrpc2.run server in
let task =
let shutdown () = s#get_status = `ReceivedExit in
Linol_lwt.Jsonrpc2.run ~shutdown server
in
match Linol_lwt.run task with
| () -> ()
| exception e ->

View file

@ -302,6 +302,10 @@ module Make (IO : IO) : S with module IO = IO = struct
IO.return
@@ Error (E (ErrorCode.InvalidRequest, "content-type must be 'utf-8'"))
(** [shutdown ()] is called after processing each request to check if the server
could wait for new messages.
When launching an LSP server using [Server.Make.server], the
natural choice for it is [s#get_status = `ReceivedExit] *)
let run ?(shutdown = fun _ -> false) (self : t) : unit IO.t =
let process_msg r =
let module M = Jsonrpc.Packet in

View file

@ -157,9 +157,14 @@ module Make (IO : IO) = struct
class virtual server =
object (self)
inherit base_server
val mutable _quit = false
val mutable status : [ `Running | `ReceivedShutdown | `ReceivedExit ] =
`Running
val docs : (DocumentUri.t, doc_state) Hashtbl.t = Hashtbl.create 16
method! must_quit = _quit
method get_status = status
(** Check if exit or shutdown request was made by the client.
@since NEXT_RELEASE *)
method find_doc (uri : DocumentUri.t) : doc_state option =
try Some (Hashtbl.find docs uri) with Not_found -> None
@ -313,7 +318,7 @@ module Make (IO : IO) = struct
match r with
| Lsp.Client_request.Shutdown ->
Log.info (fun k -> k "shutdown");
_quit <- true;
status <- `ReceivedShutdown;
IO.return ()
| Lsp.Client_request.Initialize i ->
Log.debug (fun k -> k "req: initialize");
@ -583,7 +588,7 @@ module Make (IO : IO) = struct
~old_content:(Lsp.Text_document.text old_doc)
~new_content:new_st.content
| Lsp.Client_notification.Exit ->
_quit <- true;
status <- `ReceivedExit;
IO.return ()
| Lsp.Client_notification.DidSaveTextDocument _
| Lsp.Client_notification.WillSaveTextDocument _