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

fix empty command behavior on windows #2378

Merged
merged 4 commits into from
Jan 2, 2024

Conversation

AlexeyBond
Copy link
Contributor

Plugin fails with "list index out of range" error when running on windows and a server is configured with empy command and non-empty tcp_port.

Looks like regression of #2118, but it also doesn't look like it was really fixed.

@predragnikolic
Copy link
Member

Hello @AlexeyBond ,

Can you paste the clients configuration that you have in LSP.sublime-settings that can reproduce the issue?

@AlexeyBond
Copy link
Contributor Author

Can you paste the clients configuration that you have in LSP.sublime-settings that can reproduce the issue?

LSP.sublime-settings contains the following configuration:

// Settings in here override those in "LSP/LSP.sublime-settings"
{
	"clients": {
		"test-lsp": {
			"selector": "source.lisp",
			"tcp_port": 9257,
			"enabled": true
		},
	},
}

I'm running an example server from tower-lsp on port 9257.

sock = None # type: Optional[socket.socket]
process = None # type: Optional[subprocess.Popen]

def start_subprocess() -> subprocess.Popen:
startupinfo = _fixup_startup_args(config.command)
return _start_subprocess(config.command, stdin, stdout, subprocess.PIPE, startupinfo, config.env, cwd)
Copy link
Member

@predragnikolic predragnikolic Dec 18, 2023

Choose a reason for hiding this comment

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

the bug is caused by the _fixup_startup_args function

def _fixup_startup_args(args: List[str]) -> Any:
    startupinfo = None
    if sublime.platform() == "windows":
        # ... this line bellow causes the Error because, args is `[]`
        executable_arg = args[0]

start_subprocess will be called in 2 cases:

-   startupinfo = _fixup_startup_args(config.command)


    def start_subprocess() -> subprocess.Popen:
+       startupinfo = _fixup_startup_args(config.command)
        return _start_subprocess(config.command, stdin, stdout, subprocess.PIPE, startupinfo, config.env, cwd)

    if config.listener_socket:
        assert isinstance(config.tcp_port, int) and config.tcp_port > 0
        process, sock, reader, writer = _await_tcp_connection(
            config.name, config.tcp_port, config.listener_socket, start_subprocess) # <- Case 1
    else:
        if config.command:
            process = start_subprocess() # <- Case 2 

In this PR the _fixup_startup_args will not be called in Case 2 anymore,
so the bug in that case will be fixed.

But in Case 1, when start_subprocess is called, the _fixup_startup_args will also be called, and the command will still be None. So we still have a bug there.

IMO, a better option would be to fix the _fixup_startup_args function:

def _fixup_startup_args(args: List[str]) -> Any:
+     if not args:
+         return None
    startupinfo = None
    if sublime.platform() == "windows":
        startupinfo = subprocess.STARTUPINFO()  # type: ignore
        startupinfo.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW  # type: ignore
        executable_arg = args[0]
        _, ext = os.path.splitext(executable_arg)
        if len(ext) < 1:
            path_to_executable = shutil.which(executable_arg)
            # what extensions should we append so CreateProcess can find it?
            # node has .cmd
            # dart has .bat
            # python has .exe wrappers - not needed
            for extension in ['.cmd', '.bat']:
                if path_to_executable and path_to_executable.lower().endswith(extension):
                    args[0] = executable_arg + extension
                    break
    return startupinfo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like something else is wrong with case 1: it doesn't make sense to start a subprocess with empty command. And _start_subprocess will fail when first argument is empty:

def _start_subprocess(
    args: List[str],
# ...
) -> subprocess.Popen:
    debug("starting {} in {}".format(args, cwd if cwd else os.getcwd()))
    process = subprocess.Popen( # <- Will throw if args is empty
        args=args,
        # ...
    )
    _subprocesses.add(process)
    return process

So, even with check added in _fixup_startup_args, it will still fail, but in _start_subprocess.

Comment on lines 257 to 263
if config.command:
process, sock, reader, writer = _start_client_process_and_await_connection(
config.listener_socket,
start_subprocess
)
else:
sock, reader, writer = _await_client_connection(config.listener_socket)
Copy link
Contributor Author

@AlexeyBond AlexeyBond Dec 19, 2023

Choose a reason for hiding this comment

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

I decided to split _await_tcp_connection into two functions:

  • _await_client_connection - waits for client to connect to listener socket
  • _start_client_process_and_await_connection - starts client process and simulteniously calls _await_client_connection

and call them depending on presence of command in configuration when language server is running as TCP client.

Just in case, configuration to reproduce this scenario:

// Settings in here override those in "LSP/LSP.sublime-settings"
{
	"clients": {
		"test-lsp": {
			"selector": "source.lisp",
			"tcp_port": -9257,
			"enabled": true
		},
	},
}

Language server startup will fail with timeout error if language server doesn't try to connect to editor port. Previously it would also fail with "list index out of range".

I have tested these changes with manually launched language server running in client and server modes. But haven't yet tested with server process launched by editor (non-empty command).

UPD: Tested in all scenarios - with and without command, in client and server mode. Seems to work fine in all cases.

@rchl rchl merged commit 5923210 into sublimelsp:main Jan 2, 2024
4 checks passed
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.

3 participants