Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --node-ipc support #2015

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft

Add --node-ipc support #2015

wants to merge 14 commits into from

Conversation

alecmev
Copy link
Contributor

@alecmev alecmev commented Aug 9, 2022

Fix #1612

Lots of room for refactoring and better typing and better errors and tests, but went for the cleanest diff possible. Can be tested with stock LSP-eslint by changing command to ["${node_bin}", "${server_path}", "--node-ipc"].

alecmev added a commit to alecmev/LSP-eslint that referenced this pull request Aug 9, 2022
@rchl
Copy link
Member

rchl commented Aug 9, 2022

It looks a bit rough code-quality wise but thanks, it can be polished before merging.

High-level question to @rwols, @predragnikolic, do you prefer explicit config option for enabling node-ipc or do it like here where we check for the --node-ipc argument? In theory servers might implement node-ipc without using that specific argument so I probably prefer being explicit about that.

plugin/core/transports.py Outdated Show resolved Hide resolved
plugin/core/transports.py Outdated Show resolved Hide resolved
Somehow aliasing _read to conn._read breaks it, even though it's not a
class method.
@predragnikolic
Copy link
Member

predragnikolic commented Aug 9, 2022

do you prefer explicit config option?

+1 for explicit config option

My first suggestion for the client config option name would be:

// values:  "RPC", "TCP" , "IPC"
// default: "RPC"
"process_communication": "RPC"

What do you think?

@alecmev
Copy link
Contributor Author

alecmev commented Aug 9, 2022

How about "stdio", "tcp" and "node-ipc" (as I imagine there might be other kinds of IPC out there)? Don't think RPC makes sense, since all of them are RPC.

@rchl
Copy link
Member

rchl commented Aug 9, 2022

I would think something like "transport": "..." but that would be a breaking change for the tcp case which currently doesn't require this new option to be set. So just adding a separate new option like "use_node_ipc" might be more messy but create least friction.

@rwols
Copy link
Member

rwols commented Aug 9, 2022

Yeah agreed with the boolean use_node_ipc setting for the ClientConfig.

There is further separation in "tcp": "tcp-server" and "tcp-client" :) Anyway, offtopic for this PR.

@alecmev
Copy link
Contributor Author

alecmev commented Aug 9, 2022

Thanks for all the comments! I'll rework the processor, but in the meantime, does somebody have access to Windows? The pipe is supposed be to be cross-platform, but we're going off-piste here a bit.

Edit: Re use_node_ipc, where do I put it? And should I add some sort of guard, making sure that the command is in accordance with the setting, e.g. if use_node_ipc = True then --node-ipc must be in the command?

@predragnikolic

This comment was marked as resolved.

plugin/core/types.py Outdated Show resolved Hide resolved
alecmev added a commit to alecmev/LSP-eslint that referenced this pull request Aug 10, 2022
See sublimelsp/LSP#2015

Disabled by default because stdio works in 99.(9)% of cases, so there's
no reason to switch until it's proven to be reliable.
plugin/core/transports.py Outdated Show resolved Hide resolved
@@ -136,8 +171,8 @@ def __del__(self) -> None:

def _read_loop(self) -> None:
try:
while self._reader:
payload = self._processor.read_data(self._reader)
while True:
Copy link
Contributor Author

@alecmev alecmev Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to Google this and also replicate it in REPL with io.BytesIO, but it seems that streams are always truthy? Or am I taking down a Chesterton's fence here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@predragnikolic Do you know anything about this? Do self._reader/self._writer ever become falsey?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know anything about this?

No, I don't know, I just reverted the old code.
I will let someone with more experience answer this question. :)

I can just guess and say that
maybe the ProcessTransport close method should be modified:

    def close(self) -> None:
        if not self._closed:
            self._send_queue.put_nowait(None)
            if self._socket:
                self._socket.close()
            self._closed = True
+           self._reader.close()
+           self._writer.close()
+.          self._reader = None
+.          self._writer = None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know about stdio and TCP, but the multiprocessing pipe is garbage-collectable: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.connection.Connection.close

sublime-package.json Outdated Show resolved Hide resolved
@alecmev
Copy link
Contributor Author

alecmev commented Aug 10, 2022

Thank you for all the feedback again! Please dogfood these changes before merging. ESLint (in the new mode), TypeScript, Pyright, JSON, HTML, YAML work for me so far, but you never know.

@rchl
Copy link
Member

rchl commented Aug 10, 2022

Have you verified that it still works when not using use_node_ipc? I see some weird crashes without even using it but don't have time to investigate right now.

@rchl rchl marked this pull request as draft August 10, 2022 07:45
@predragnikolic
Copy link
Member

predragnikolic commented Aug 10, 2022

I am also seeing errors,
I've time to look at this now:

Errors from ST console
Exception AttributeError: "'ProcessTransport' object has no attribute '_closed'" in <bound method ProcessTransport.__del__ of <LSP.plugin.core.transports.ProcessTransport object at 0x1035bac18>> ignored
Error handling request
Traceback (most recent call last):
  File "/Users/codetribe/Library/Application Support/Sublime Text/Packages/LSP/plugin/core/sessions.py", line 1930, in on_payload
    handler(result, req_id)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Packages/LSP/plugin/core/sessions.py", line 1677, in m_client_registerCapability
    watcher = self._watcher_impl.create(folder.path, [pattern], kind, ignores, self)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 93, in create
    return file_watcher.register_watcher(root_path, patterns, events, ignores, handler)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 121, in register_watcher
    self._on_watcher_added(root_path, patterns, events, ignores, handler)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 134, in _on_watcher_added
    self._start_process()
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 183, in _start_process
    'lspwatcher', process, None, process.stdout, process.stdin, process.stderr, StringTransportHandler(), self)
TypeError: __init__() takes 7 positional arguments but 9 were given

----

Error handling request
Traceback (most recent call last):
  File "/Users/codetribe/Library/Application Support/Sublime Text/Packages/LSP/plugin/core/sessions.py", line 1930, in on_payload
    handler(result, req_id)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Packages/LSP/plugin/core/sessions.py", line 1677, in m_client_registerCapability
    watcher = self._watcher_impl.create(folder.path, [pattern], kind, ignores, self)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 93, in create
    return file_watcher.register_watcher(root_path, patterns, events, ignores, handler)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 121, in register_watcher
    self._on_watcher_added(root_path, patterns, events, ignores, handler)
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 134, in _on_watcher_added
    self._start_process()
  File "/Users/codetribe/Library/Application Support/Sublime Text/Installed Packages/LSP-file-watcher-chokidar.sublime-package/watcher.py", line 183, in _start_process
    'lspwatcher', process, None, process.stdout, process.stdin, process.stderr, StringTransportHandler(), self)
TypeError: __init__() takes 7 positional arguments but 9 were given

Screenshot 2022-08-10 at 09 23 49


Disabling LSP-file-watcher-chokidar will fix the issue.

@rchl
Copy link
Member

rchl commented Aug 10, 2022

Then I would try to make changes here so that it's not a breaking change for chokidar watcher. Maybe possible by making the newly added arguments optional. Watcher is using those imports: https://github.com/sublimelsp/LSP-file-watcher-chokidar/blob/1687cbf784dbad6b2fcaa4b3905c414a35cc9900/watcher.py#L7-L11

@rchl
Copy link
Member

rchl commented Aug 10, 2022

Alternatively, if the former would be too messy, we can update the watcher to use the new API but it should be done in a way that supports both new and old API (for a while, at least).

@predragnikolic
Copy link
Member

predragnikolic commented Aug 10, 2022

If @alecmev don't mind I would push the change based on Rafal comment? :)

If you mind, feel free to revert the last commit.

def __init__(self, name: str, process: subprocess.Popen, socket: Optional[socket.socket], reader: IO[bytes],
writer: IO[bytes], stderr: Optional[IO[bytes]], processor: AbstractProcessor[T],
def __init__(self, name: str, process: subprocess.Popen, socket: Optional[socket.socket], reader: Any,
writer: Any, stderr: Optional[IO[bytes]], processor: AbstractProcessor[T],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good that instead of any I used a better type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be solved with generics, I think, but it would again require downstream changes, since it would change AbstractProcessor's signature.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some local changes to clean up the types but I'm yet to find a solution that is fully properly typed. It's complicated.

In any case, that would be something for another PR.

@rwols
Copy link
Member

rwols commented Aug 12, 2022

on Windows, LSP-json stops right away:

:: --> LSP-json initialize(1): {'workspaceFolders': [{'name': 'Packages', 'uri': 'file:///C:/Users/Raoul/AppData/Roaming/Sublime%20Text%203/Packages'}], 'clientInfo': {'name': 'Sublime Text LSP', 'version': '1.17.0'}, 'rootPath': 'C:\\Users\\Raoul\\AppData\\Roaming\\Sublime Text 3\\Packages', 'processId': 2224, 'capabilities': {'window': {'showDocument': {'support': True}, 'workDoneProgress': True, 'showMessage': {'messageActionItem': {'additionalPropertiesSupport': True}}}, 'general': {'regularExpressions': {'engine': 'ECMAScript'}, 'markdown': {'version': '3.2.2', 'parser': 'Python-Markdown'}}, 'workspace': {'semanticTokens': {'refreshSupport': True}, 'codeLens': {'refreshSupport': True}, 'workspaceFolders': True, 'didChangeConfiguration': {'dynamicRegistration': True}, 'configuration': True, 'symbol': {'dynamicRegistration': True, 'tagSupport': {'valueSet': [1]}, 'symbolKind': {'valueSet': [4, 5, 21, 15, 3, 1, 17, 18, 12, 7, 11, 16, 2, 25, 19, 9, 13, 10, 24, 20, 22, 23, 6, 14, 8, 26]}}, 'workspaceEdit': {'documentChanges': True, 'failureHandling': 'abort'}, 'applyEdit': True, 'executeCommand': {}}, 'textDocument': {'selectionRange': {'dynamicRegistration': True}, 'semanticTokens': {'tokenTypes': ['keyword', 'macro', 'typeParameter', 'comment', 'class', 'type', 'function', 'enumMember', 'interface', 'struct', 'decorator', 'regexp', 'operator', 'namespace', 'string', 'variable', 'enum', 'event', 'number', 'parameter', 'property', 'method', 'modifier'], 'formats': ['relative'], 'overlappingTokenSupport': False, 'multilineTokenSupport': True, 'requests': {'full': {'delta': True}, 'range': True}, 'tokenModifiers': ['deprecated', 'modification', 'static', 'declaration', 'abstract', 'defaultLibrary', 'definition', 'documentation', 'readonly', 'async'], 'dynamicRegistration': True, 'augmentsSyntaxTokens': True}, 'codeAction': {'resolveSupport': {'properties': ['edit']}, 'dynamicRegistration': True, 'dataSupport': True, 'codeActionLiteralSupport': {'codeActionKind': {'valueSet': ['quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source.organizeImports']}}, 'disabledSupport': True}, 'hover': {'dynamicRegistration': True, 'contentFormat': ['markdown', 'plaintext']}, 'signatureHelp': {'contextSupport': True, 'dynamicRegistration': True, 'signatureInformation': {'parameterInformation': {'labelOffsetSupport': True}, 'activeParameterSupport': True, 'documentationFormat': ['markdown', 'plaintext']}}, 'publishDiagnostics': {'relatedInformation': True, 'versionSupport': True, 'tagSupport': {'valueSet': [2, 1]}, 'dataSupport': True, 'codeDescriptionSupport': True}, 'declaration': {'dynamicRegistration': True, 'linkSupport': True}, 'documentHighlight': {'dynamicRegistration': True}, 'synchronization': {'willSaveWaitUntil': True, 'dynamicRegistration': True, 'willSave': True, 'didSave': True}, 'rangeFormatting': {'dynamicRegistration': True}, 'references': {'dynamicRegistration': True}, 'colorProvider': {'dynamicRegistration': True}, 'documentSymbol': {'hierarchicalDocumentSymbolSupport': True, 'dynamicRegistration': True, 'tagSupport': {'valueSet': [1]}, 'symbolKind': {'valueSet': [4, 5, 21, 15, 3, 1, 17, 18, 12, 7, 11, 16, 2, 25, 19, 9, 13, 10, 24, 20, 22, 23, 6, 14, 8, 26]}}, 'completion': {'insertTextMode': 2, 'dynamicRegistration': True, 'completionItem': {'deprecatedSupport': True, 'insertTextModeSupport': {'valueSet': [2]}, 'resolveSupport': {'properties': ['detail', 'documentation', 'additionalTextEdits']}, 'tagSupport': {'valueSet': [1]}, 'snippetSupport': True, 'documentationFormat': ['markdown', 'plaintext'], 'labelDetailsSupport': True}, 'completionItemKind': {'valueSet': [14, 19, 18, 7, 9, 1, 15, 11, 3, 10, 8, 22, 17, 24, 4, 6, 13, 16, 23, 20, 2, 21, 12, 5, 25]}}, 'formatting': {'dynamicRegistration': True}, 'typeDefinition': {'dynamicRegistration': True, 'linkSupport': True}, 'definition': {'dynamicRegistration': True, 'linkSupport': True}, 'codeLens': {'dynamicRegistration': True}, 'documentLink': {'tooltipSupport': True, 'dynamicRegistration': True}, 'implementation': {'dynamicRegistration': True, 'linkSupport': True}, 'rename': {'prepareSupport': True, 'dynamicRegistration': True}}}, 'rootUri': 'file:///C:/Users/Raoul/AppData/Roaming/Sublime%20Text%203/Packages', 'initializationOptions': {'handledSchemaProtocols': ['https', 'http', 'file'], 'provideFormatter': True, 'customCapabilities': {'rangeFormatting': {'editLimit': 1000}}}}
LSP-json: child_process.js:135
LSP-json:   p.open(fd);
LSP-json:     ^
LSP-json: 
LSP-json: Error: EBADF: bad file descriptor, uv_pipe_open
LSP-json:     at Object._forkChild (child_process.js:135:5)
LSP-json:     at setupChildProcessIpcChannel (internal/bootstrap/pre_execution.js:355:30)
LSP-json:     at prepareMainThreadExecution (internal/bootstrap/pre_execution.js:52:3)
LSP-json:     at internal/main/run_main_module.js:7:1 {
LSP-json:   errno: -4083,
LSP-json:   code: 'EBADF',
LSP-json:   syscall: 'uv_pipe_open'
LSP-json: }

I'll investigate some more.

@rwols
Copy link
Member

rwols commented Aug 12, 2022

The pass_fds parameter of subprocess.Popen is POSIX only, so probably the subprocess doesn't see the child file descriptor at all: https://docs.python.org/3/library/subprocess.html#subprocess.Popen

@alecmev
Copy link
Contributor Author

alecmev commented Aug 13, 2022

Thank you for testing!

Sorry, for some reason I was under impression that Windows was POSIX-compliant, but turns out it was just a subsystem, available only in professional editions, and removed a long time ago too.

There is lpAttributeList.handle_list, which is a Windows equivalent of pass_fds, but it is available only in Python 3.7+, without any Python-only workarounds (needs native code). I went ahead and just set close_fds to False explicitly, letting the child inherit all handles. This has security implications, of course. Does plugin_host isolate the plugins from each other? Do we treat language servers as "trusted"? If this is a concern, then we can attempt something like this. Please let me know! Which is already effectively the case in 3.3 on Windows, since stdio is always non-None.

When is LSP switching to 3.8?

Copy link
Member

@rchl rchl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for the future: force-pushing does nothing good. Just complicates things for reviewers (granted, in that case the changes are pretty small so it's no big deal).

PR's are squashed before merging anyway so there is no need to clean up commits before.

Comment on lines 255 to 256
# https://github.com/python/cpython/blob/17bf6b4671ec02d80ad29b278639d5307baddeb5/Lib/subprocess.py#L706
close_fds = True if sys.version_info >= (3, 8, 0) else subprocess._PLATFORM_DEFAULT_CLOSE_FDS # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PLATFORM_DEFAULT_CLOSE_FDS doesn't sound like a public API, judging from the leading underscore

It looks like just omitting that argument in the subprocess constructor would be the way to go?

Comment on lines 61 to 66
return json.dumps(
data,
ensure_ascii=False,
check_circular=False,
separators=(',', ':')
).encode('utf-8')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fits one line now :)

_buf = bytearray()
_lines = 0

def write_data(self, connection: multiprocessing.connection._ConnectionBase, data: Dict[str, Any]) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really no way to use public type here? Underscore denotes a private property.

Copy link
Member

@predragnikolic predragnikolic Aug 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ConnectionBase was used because multiprocessing.Pipe() uses that.

class NodeIpcPipe():
    def __init__(self) -> None:
        parent_connection, child_connection = multiprocessing.Pipe()
        self.parent_connection = parent_connection
        # type: _ConnectionBase
        self.child_connection = child_connection
        # type _ConnectionBase

I looked a bit at the multiprocessing.Pipe()

    def Pipe(duplex=True):
        '''
        Returns pair of connection objects at either end of a pipe
        '''
        if duplex:
            s1, s2 = socket.socketpair()
            s1.setblocking(True)
            s2.setblocking(True)
            c1 = Connection(s1.detach())
            c2 = Connection(s2.detach())
        else:
            fd1, fd2 = os.pipe()
            c1 = Connection(fd1, writable=False)
            c2 = Connection(fd2, readable=False)

        return c1, c2

and found that maybe we can use the multiprocessing.connection.Connection as the type.

as Connection extends _ConnectionBase, and it doesn't add any public methods

class Connection(_ConnectionBase):
    """
    Connection class based on an arbitrary file descriptor (Unix only), or
    a socket handle (Windows).
    """

    if _winapi:
        def _close(self, _close=_multiprocessing.closesocket):
            ...
    else:
        def _close(self, _close=os.close):
            ...

    def _send(self, buf, write=_write):
        ...
    def _recv(self, size, read=_read):
        ...
    def _send_bytes(self, buf):
        ...
    def _recv_bytes(self, maxsize=None):
        ...
    def _poll(self, timeout):
        ...

writer.writelines(("Content-Length: {}\r\n\r\n".format(len(body)).encode('ascii'), body))
writer.flush()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially breaking for chokidar watcher. We need to add a flush there. I guess duplicate flush() might be ok to avoid adding too much logic there.


def read_data(self, connection: multiprocessing.connection._ConnectionBase) -> Optional[Dict[str, Any]]:
while self._lines == 0:
chunk = connection._read(connection.fileno(), 65536) # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also private API...

I'm not saying it's unacceptable but is there really no higher level API exposed for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also bothers me. Perhaps switching to some public mechanism also abstracts away the burden of choosing the buffer size? (Currently set to 65KB)

Copy link
Member

@rwols rwols left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool overall. With the latest changes using close_fds it still doesn't want to start on Windows, but I'll investigate more. I tried setting the stdout and stdin subprocess handles directly to the parent and child file descriptors but that doesn't seem to work.


class NodeIpcProcessor(AbstractProcessor[Dict[str, Any]]):
_buf = bytearray()
_lines = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these two properties be part of the class instance, instead of the class? If I have LSP-dockerfile, LSP-stylelint, and LSP-typescript running with node IPC, wouldn't these overwrite each other in unpredictable ways? Maybe I'm not understanding something.

Copy link
Member

@rchl rchl Aug 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it should work as expected (in most cases ;)) due to how Python works with those.

When instance of the class reads those properties it looks them up first in the instance and then in the class. When writing it always writes to the instance property (creates it if necessary).

The only issue would be if the instance would read a class property (for example self._buf) and then mutate that property (self._buf.append(...) or whatever methods that supports). Then it would mutate the class property.

So I guess it's still safer to set those on the instance.


def read_data(self, connection: multiprocessing.connection._ConnectionBase) -> Optional[Dict[str, Any]]:
while self._lines == 0:
chunk = connection._read(connection.fileno(), 65536) # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

65536 looks like a rather large buffer (65KB), have you tried 4KB? What are the performance implications compared to the standard transport?

@@ -220,27 +245,37 @@ def _stderr_loop(self) -> None:


# Can be a singleton since it doesn't hold any state.
json_rpc_processor = JsonRpcProcessor()
standard_processor = StandardProcessor()
node_ipc_processor = NodeIpcProcessor()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm unsure whether this can actually be a singleton.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right. The standard processor wasn't meant to have any instance properties so NodeIpc shouldn't either.

tcp_port=s.get("tcp_port"),
auto_complete_selector=s.get("auto_complete_selector"),
# Default to True, because an LSP plugin is enabled iff it is enabled as a Sublime package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"iff" is short for "if and only if" :) but keep the change, no one really cares.

@rwols
Copy link
Member

rwols commented Aug 13, 2022

When is LSP switching to 3.8?

See: #1389

@rwols
Copy link
Member

rwols commented Aug 14, 2022

Just to give an update:

@rchl
Copy link
Member

rchl commented Aug 15, 2022

Again, don't have time to investigate right now, but it appears to break LSP-chokidar, even when not using any of this functionality explicitly. No didChangeWatchedFiles notifications trigger anymore.

EDIT: Actually that is likely due to what I've already mentioned -- missing flush due to a breaking change.

@rwols
Copy link
Member

rwols commented Aug 21, 2022

I gave up on the ctypes approach as it seems like a lot of work :( Maybe this encourages @wbond or @deathaxe to hurry up with the py38 dependencies problems? ;)

@predragnikolic
Copy link
Member

As much as I would like to get this merged.

I would like to ask,
It seems like python 3.8 will enable to solve this issue, #2015 (comment)
do you think it is best to wait for 3.8 before processing further with this PR?

@rwols
Copy link
Member

rwols commented Sep 11, 2022

Yep. Least effort is #1389.

@rchl rchl marked this pull request as draft December 11, 2022 13:03
@deathaxe
Copy link
Contributor

Maybe this encourages @wbond or @deathaxe to hurry up with the py38 dependencies problems? ;)

Just for the record. We are done. 🤣

@rwols
Copy link
Member

rwols commented Sep 14, 2024

I was working on this in https://github.com/sublimelsp/LSP/tree/node-ipc, but still could not get the "named pipes" to work on Windows. If you have any ideas or insights, it would be appreciated.

Working on this I started to realize that the "duplex pipes" implementation is really just another socket or kind of file descriptor. So I don't see a theoretical performance benefit over plain stdout/stdin. I guess it may help in avoiding a bug in the eslint language server. What other benefits are there with respect to duplex pipes over stdout/stdin ?

@alecmev
Copy link
Contributor Author

alecmev commented Sep 14, 2024

I guess it may help in avoiding a bug in the eslint language server. What other benefits are there with respect to duplex pipes over stdout/stdin ?

It comes down to this, IMO:

such content should not be included in stdout so in theory that is something that should be fixed in https://github.com/microsoft/vscode-eslint or maybe even eslint itself

This is possible (ESLint could invoke configuration files and plugins in a separate process and pipe the stdout only if asked to, and vscode-eslint could communicate with eslint/lib/api.js through a separate process too), but I'm not sure if such changes will ever be accepted, since both of them work fine as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LSP-eslint exits unexpectedly in ST 4098 due to non-conforming stdout input
6 participants