openlawlibrary / pygls

A pythonic generic language server
https://pygls.readthedocs.io/en/latest/
Apache License 2.0
563 stars 102 forks source link

Add a named pipe support to `Server` #403

Closed karthiknadig closed 9 months ago

karthiknadig commented 10 months ago

The IO mode is sometimes impacted be certain libraries writing directly to stdout. We have seen this occur with python logging library that has been configured to output to stdout.

With sockets, users have firewall rules that can sometimes prevent sockets from being opened.

The language client for VS Code supports using named pipe, where a named pipe is created on the node side and passes it into the server.

The feature request is to add a start_pipe method to Server

alcarney commented 10 months ago

I'm not familiar with named pipes... how would they look in Python? Would the server basically be given a pair of "files" (/dev/in_pipe, /dev/out_pipe) that it could read/write to?

Is this something we can support with the existing start_io function by passing in the relevant objects? e.g. server.start_io(stdin=in_pipe, stdout=out_pipe)

karthiknadig commented 10 months ago

You can treat the name pipe like a file in python. So you can use open in python to consume it. I will add more technical details on this, as there may be subtle differences between windows vs linux on how the pipes are handled.

karthiknadig commented 10 months ago

This seems to be working for me:

import argparse
from pygls import server

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--pipe', action='store', dest='pipe', help='Named pipe to connect to', required=True)
    return parser.parse_args()

LSP_SERVER = server.LanguageServer(
    name="demo-server", version="v0.1.0", max_workers=5
)

if __name__ == "__main__":
    named_pipe = parse_args().pipe
    with open(named_pipe, 'r+b') as f:
        LSP_SERVER.start_io(f, f)

I have only tested this on windows.

tombh commented 10 months ago

So in theory Pygls already supports this? In which case you'd like to see a more formal API that makes it easier?

karthiknadig commented 10 months ago

So in theory Pygls already supports this? In which case you'd like to see a more formal API that makes it easier?

Correct, a start_pipe API that makes this easier, and defines the expectation on what is needed from the pipe. Going forward this would be my recommendation. We are also switching servers in few other languages to named pipes as well. Random packages injecting themselves using pth files and printing to stdout has been a pain to detect and debug. Turns out this is a common problem for most languages.

tombh commented 10 months ago

Maybe we could even default to named pipes!? I see that there's a package that can create cross-platform named pipes: https://pypi.org/project/namedpipe Whether we'd use that or not, at least demonstrates that it's possible to create named pipes without user input. Of course, we can also have an option for providing a pre-existing pipe.

karthiknadig commented 10 months ago

For the purposes I have mentioned we don't need to create one. Currently creating a named pipe in python for cross-platform requires calling native APIs from python. But consuming a pipe created elsewhere is easier.

You would likely only use it to test the namedpipe bits.

karthiknadig commented 9 months ago

Closing this for now, I will re-open this and provide an implementation when the client side is ready. See https://github.com/microsoft/vscode-languageserver-node/issues/1351

Basically, on windows the LS client in VS Code uses NamedPipes, but on Unix it uses Unix Domain Socket (a socket with AF_UNIX set when creating it). My assumption when I created this was that it would be using fifo (named pipe in unix). We need to do some work on LS Client side before we can fully rely on fifo. so, for now this can be closed.