chore: ever so slightly improve watchdog doc

This commit is contained in:
Marc Huisinga 2020-12-06 18:40:06 +01:00 committed by Sebastian Ullrich
parent 8658577731
commit 0dca44da30
2 changed files with 37 additions and 43 deletions

View file

@ -12,8 +12,7 @@ import Lean.Data.Json
/-! Implementation of JSON-RPC 2.0 (https://www.jsonrpc.org/specification)
for use in the LSP server. -/
namespace Lean
namespace JsonRpc
namespace Lean.JsonRpc
open Json
open Std (RBNode)
@ -179,8 +178,7 @@ instance : FromJson Message := ⟨fun j => do
let data? := err.getObjVal? "data"
pure (Message.responseError id code message data?))⟩
end JsonRpc
end Lean
end Lean.JsonRpc
namespace IO.FS.Stream

View file

@ -74,23 +74,23 @@ section Utils
def workerCfg : Process.StdioConfig := {
stdin := Process.Stdio.piped
stdout := Process.Stdio.piped
/- We pass workers' stderr through to the editor. -/
-- We pass workers' stderr through to the editor.
stderr := Process.Stdio.inherit
}
-- Events that a forwarding task of a worker signals to the main task
/-- Events that a forwarding task of a worker signals to the main task -/
inductive WorkerEvent where
| terminated
| crashed (e : IO.Error)
inductive WorkerState where
-- The watchdog can detect a crashed file worker in two places: When trying to send a message to the file worker
-- and when reading a request reply.
-- In the latter case, the forwarding task terminates and delegates a `crashed` event to the main task.
-- Then, in both cases, the file worker has its state set to `crashed` and requests that are in-flight are errored.
-- Upon receiving the next packet for that file worker, the file worker is restarted and the packet is forwarded
-- to it. If the crash was detected while writing a packet, we queue that packet until the next packet for the file
-- worker arrives.
/- The watchdog can detect a crashed file worker in two places: When trying to send a message to the file worker
and when reading a request reply.
In the latter case, the forwarding task terminates and delegates a `crashed` event to the main task.
Then, in both cases, the file worker has its state set to `crashed` and requests that are in-flight are errored.
Upon receiving the next packet for that file worker, the file worker is restarted and the packet is forwarded
to it. If the crash was detected while writing a packet, we queue that packet until the next packet for the file
worker arrives. -/
| crashed (queuedMsgs : Array JsonRpc.Message)
| running
@ -108,7 +108,7 @@ section FileWorker
proc : Process.Child workerCfg
commTask : Task WorkerEvent
state : WorkerState
-- NOTE: this should not be mutated outside of namespace FileWorker
-- This should not be mutated outside of namespace FileWorker, as it is used as shared mutable state
pendingRequestsRef : IO.Ref PendingRequestMap
namespace FileWorker
@ -153,7 +153,7 @@ section ServerM
structure ServerContext where
hIn hOut hLog : FS.Stream
fileWorkersRef : IO.Ref FileWorkerMap
/- We store these to pass them to workers. -/
-- We store these to pass them to workers.
initParams : InitializeParams
workerPath : String
@ -182,19 +182,19 @@ section ServerM
let rec loop : IO WorkerEvent := do
try
let msg ← fw.readMessage
-- NOTE: Writes to Lean I/O channels are atomic, so these won't trample on each other.
-- Writes to Lean I/O channels are atomic, so these won't trample on each other.
o.writeLspMessage msg
loop
catch err =>
-- NOTE: if writeLspMessage from above errors we will block here, but the main task will
-- If writeLspMessage from above errors we will block here, but the main task will
-- quit eventually anyways if that happens
let exitCode ← fw.proc.wait
if exitCode = 0 then
-- worker was terminated
-- Worker was terminated
fw.errorPendingRequests o ErrorCode.contentModified "File header changed or file was closed"
return WorkerEvent.terminated
else
-- worker crashed
-- Worker crashed
fw.errorPendingRequests o ErrorCode.internalError
"Server process of file crashed, likely due to a stack overflow in user code"
return WorkerEvent.crashed err
@ -212,7 +212,7 @@ section ServerM
args := #["--worker"]
}
let pendingRequestsRef ← IO.mkRef (RBMap.empty : PendingRequestMap)
-- the task will never access itself, so this is fine
-- The task will never access itself, so this is fine
let commTaskFw : FileWorker := {
doc := ⟨version, text, headerAst⟩
proc := workerProc
@ -237,12 +237,12 @@ section ServerM
updateFileWorkers uri fw
def terminateFileWorker (uri : DocumentUri) : ServerM Unit := do
-- the file worker must have crashed just when we were about to terminate it!
-- that's fine - just forget about it then.
-- (on didClose we won't need the crashed file worker anymore,
-- when the header changed we'll start a new one right after
-- anyways and when we're shutting down the server
-- it's over either way.)
/- The file worker must have crashed just when we were about to terminate it!
That's fine - just forget about it then.
(on didClose we won't need the crashed file worker anymore,
when the header changed we'll start a new one right after
anyways and when we're shutting down the server
it's over either way.) -/
try (←findFileWorker uri).writeMessage (Message.notification "exit" none)
catch err => ()
eraseFileWorker uri
@ -250,14 +250,10 @@ section ServerM
def handleCrash (uri : DocumentUri) (queuedMsgs : Array JsonRpc.Message) : ServerM Unit := do
updateFileWorkers uri { ←findFileWorker uri with state := WorkerState.crashed queuedMsgs }
/- Crashes are dealt with in two steps: First, when the crash is detected, handleCrash
is called to mark the respective FileWorker as having crashed.
This can occur in two places: The forwarding task (while reading from the file worker)
and the main task (while trying to send packets to the file worker).
Then, when another message for that FileWorker is received, it is restarted.
This way, we avoid crash loops.
Messages that couldn't be sent can be queued up via the queueFailedMessage flag and
will be discharged after the FileWorker is restarted. -/
/-- Tries to write a message, sets the state of the FileWorker to `crashed` if it does not succeed
and restarts the file worker if the `crashed` flag was already set.
Messages that couldn't be sent can be queued up via the queueFailedMessage flag and
will be discharged after the FileWorker is restarted. -/
def tryWriteMessage [Coe α JsonRpc.Message] (uri : DocumentUri) (msg : α) (writeAction : FileWorker → α → IO Unit)
(queueFailedMessage := true) : ServerM Unit := do
let fw ← findFileWorker uri
@ -294,11 +290,11 @@ end ServerM
section NotificationHandling
def handleDidOpen (p : DidOpenTextDocumentParams) : ServerM Unit :=
let doc := p.textDocument
-- NOTE(WN): `toFileMap` marks line beginnings as immediately following
-- "\n", which should be enough to handle both LF and CRLF correctly.
-- This is because LSP always refers to characters by (line, column),
-- so if we get the line number correct it shouldn't matter that there
-- is a CR there.
/- NOTE(WN): `toFileMap` marks line beginnings as immediately following
"\n", which should be enough to handle both LF and CRLF correctly.
This is because LSP always refers to characters by (line, column),
so if we get the line number correct it shouldn't matter that there
is a CR there. -/
startFileWorker doc.uri doc.version doc.text.toFileMap
def handleDidChange (p : DidChangeTextDocumentParams) : ServerM Unit := do
@ -315,9 +311,9 @@ section NotificationHandling
let (newDocText, _) := foldDocumentChanges changes oldDoc.text
let newHeaderAst ← parseHeaderAst newDocText.source
if newHeaderAst != oldDoc.headerAst then
-- TODO(WN): we should amortize this somehow
-- when the user is typing in an import, this
-- may rapidly destroy/create new processes
/- TODO(WN): we should amortize this somehow
when the user is typing in an import, this
may rapidly destroy/create new processes -/
terminateFileWorker doc.uri
startFileWorker doc.uri newVersion newDocText
else
@ -378,7 +374,7 @@ section MainLoop
let clientTask ← (←IO.asTask readMsg).map $ fun
| Except.ok ev => ev
| Except.error e => ServerEvent.ClientError e
clientTask
return clientTask
partial def mainLoop (clientTask : Task ServerEvent) : ServerM Unit := do
let st ← read