Conversation
* Create a thin I/O device that forwards to `:standard_error` and register that as the `:user` I/O device. * Give GenLSP the original `:stdout` I/O device. * This `IO.` module calls from corrupting the connection and allows their use in debugging, where language clients make stderr available.
02f4246 to
7c3026a
Compare
|
Is there any cases where the expert node is printing to stdout on accident? or is this mostly about the untrusted engine code from the user's project and dependencies? On Next LS, all I did was make sure the engine node used its own stdout instead of redirecting to the server node: https://github.com/elixir-tools/next-ls/blob/eb47c98eef92ffe2b369c7c2bf56ce63b837f949/priv/monkey/_next_ls_private_compiler.ex#L1072, which I think would solve the issue here. |
|
3 outstanding items here:
|
My acute issue was due to compilation messages coming back to the manager node via (e)rpc. I don't know if there's any cases today where the expert node is printing to stdout on accident, but it's been a historically recurring issue. My motivation here is to factor out the vector entirely and end the whack-a-mole of this problem. Another practical upshot is that this lets us use the
|
|
I've refined this draft further into #824, which takes a different approach. In retrospect: group_leader is used for more than just strictly I/O routing, so manipulating it is cause for uncertainty. The problem was that the proper API for this sort of thing is undocumented (discussed in the new PR), so I didn't find it right away. It's much more the correct path though. |
…rt. (#824) *Continued from: #808 ## The Problem Expert uses stdio as the communication bus for LSP as is customary for language servers. A problem is that any writes to stdout that don't strictly follow the protocol will break it. This error case manifests as an explicit server shutdown in the best case scenario (client shuts down the server), and in the worst case, obfuscated errors like `Header must provide a Content-Length property`. This problem persists today in Expert where the manager node makes (e)rpc calls to the project node, which redirects the project node's standard output to the manager node, which can corrupt the LSP connection. This problem is ephemeral and hard to replicate, and ~~I suspect~~ is the cause of several issues, including but not limited to #366 (comment), #539, #744, and #781. ## The Solution OTP has an undocumented but long supported `-user` flag for providing an alternative `:user` [I/O device](https://www.erlang.org/doc/apps/stdlib/io_protocol.html) at boot. [iex notably uses this](https://github.com/elixir-lang/elixir/blob/a0b17a355a77a87884be5df4052f49b76ab8031f/bin/iex#L38), so it's about as well supported as an undocumented API can get. We can provide our own user device that redirect nominal `:stdio` writes to `:standard_error` while giving our GenLSP adapter exclusive usage of `:standard_out`. --------- Co-authored-by: doorgan <dorgandash@gmail.com>


The Problem
Expert uses stdio as the communication bus for LSP as is customary for language servers. A problem is that any writes to stdout that don't strictly follow the protocol will break it. This error case manifests as an explicit server shutdown in the best case scenario (client shuts down the server), and in the worst case, obfuscated errors like
Header must provide a Content-Length property.This problem persists today in Expert where the manager node makes (e)rpc calls to the project node, which redirects the project node's standard output to the manager node, which can corrupt the LSP connection. This problem is ephemeral and hard to replicate, and
I suspectis the cause of several issues, including but not limited to #366 (comment), #539, #744, and #781.The Solution
:standard_errorand register that as the:userI/O device.:stdoutI/O device.This prevents
IOmodule calls from corrupting the:stdoutconnection by routing their output to:standard_errorwhich language clients handle. In the case of Visual Studio Code, the error writes appears as messages in the LSP output, which is especially useful for debugging, since the output is colocated with the LSP message logs.This effectively closes this error path by isolating stdio to the language server output, so we don't have to play whack-a-mole with every potential write to
:stdoutgoing forward.