tools/jsonrpc2: permit func enqueuing into message daisychain
The existing JSONRPC AsyncHandler creates an infinite queue of messages
received from the client. Every message received instantly spawns a new
go-routine, which waits for the previous go-routine to finish work
before starting processing of its own message. This chain of go-routines
is synchronised via channels, which act as memory barriers.
Consequently, although every message the LSP server processes uses a
different go-routine, there is no need for additional synchronisation;
conceptually, the LSP remains single-threaded.
JSONRPC, and the LSP protocol, are designed to be bi-directional: either
agent can call the other. This leads to the possibility that both agents
call each other at the same time, and then block, each waiting for the
reply to their call.
There are currently two places, both in [server.UpdateWatchedFiles]
where the server makes calls to the client, but soon there will be more.
If the client behaves badly and does not reply, then this can block the
server. Adding timeouts on these calls, which we were missing, will
help; plus we don't particularly care about the response to these calls
- it's either an error or a void result, neither of which we currently
care about.
Nevertheless, testing shows that we significantly improve the odds of
these (and other) calls to the client working (read: working around
slightly faulty editors) if we avoid making calls to the client when we
know there's a queue of unprocessed messages (notifications, calls etc)
from the client. To do this, we enqueue the function making the call
onto the end of the queue of messages from the client. This is the
EnqueueHandler. This is never going to be perfect: there's always the
chance that the client and LSP both make calls at the same time. But if
we already know there's a call from the client arrived and pending
processing (thus the client is likely blocked waiting for a reply), then
we now always delay making calls back to the client until after the
client's calls are processed.
This mechanism also means arbitrary callback functions can be safely
injected into the processing queue without the need for any additional
synchronisation mechanism (locks etc).
Signed-off-by: Matthew Sackman <matthew@cue.works>
Change-Id: Ifbf525d23df5df70b64e96f55a1dd58a229a9770
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1233760
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>