denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
96.93k stars 5.35k forks source link

Language server exits abruptly when run in a Docker container #22012

Open Ravenstine opened 9 months ago

Ravenstine commented 9 months ago

Version: Deno 1.39.4

Environment

aarch64 linux Docker For Mac macOS Monterey 12.6.1 Macbook Pro (M1 Max)

Issue

When containerized, deno lsp runs with i/o working initially before exiting with exit code 1 after a number of seconds.

Details

It should be possible to run the lsp command within a container and use it either within that container or from the host of the container. This would be desirable to me because it would allow the VS Code extension to rely on the same Deno binary being used by the containers for my application, allowing me to scope versions of Deno to my projects.

Here is an example implementation of what I'm talking about which reproduces the issue. Instructions are included in that repo.

In VS Code, go to View > Output to and select "Deno Language Server" to observe the output, which I've configured to be more verbose in the settings file for the workspace.

What you'll witness is that the containerized language server actually works for several seconds before exiting and being restarted by the extension until it stops at 5 crashes. In the main.ts file, you can see that hovering over the code will show language information depending on whether the language server is in a good state or is starting or crashing. I have the Deno extension turned off globally, so I know that it's not accidentally filling in when the instance in the workspace fails.

So far, I can't tell why lsp would have any problem in this situation.

I did an experiment where, in the path of execution, I added a Deno script that pipes to and from the actual lsp command (using a subprocess) in order to see whether Deno is the source of the problem or Docker. The result is that the lsp command is returning an exit code of 1 with no other information, and there's nothing about Docker that is spontaneously exiting or whatever. If that were not the case, then the main process of my intermediary script should have exited, but only the subprocess exited.

The way Docker handles volumes also does not appear to be a factor. Upon modifying the project to not use volumes but copy the project contents into the Docker Image, and then rebuilding, the problem still occurs.

I have tried different versions of Deno (going back to 1.37) as well as different Docker image variants (Debian, Alpine, Ubuntu, Distroless, etc.) to similar effect. Using an Alpine base image for aarch64 and installing Deno from the Alpine repo also does the same thing; though in that case it seems like lsp is a bit longer-lived before it exits abruptly.

If what I'm trying to achieve currently isn't supported, I think it's a legitimate use case that should be considered. At the moment, I don't have any reason to believe that Docker is the source of the issue other than that it's the environment in which the issue can be reproduced; plenty of software can run identically in containers and receive i/o without unique difficulties.

UPDATE: The only Deno version I've tried that doesn't seem to encounter any issues with lsp in Docker is v1.10.3. That's as far back as I can go in terms of pre-built Docker images. The next version, v1.11.0, has what seems like a similar abrupt exiting issue.

valpackett commented 8 months ago

I'm seeing the same problem with Flatpak (I'm using the org.freedesktop.Platform as a quick way to get a glibc container to run a prebuilt deno binary on @chimera-linux heh)…

2024-01-30T22:59:21.833 helix_lsp::transport [ERROR] deno err <- "Server ready.\n"
2024-01-30T22:59:30.130 helix_lsp::transport [ERROR] deno err: <- StreamClosed
2024-01-30T22:59:30.796 helix_lsp::transport [ERROR] deno err: <- IO(Os { code: 32, kind: BrokenPipe, message: "Broken pipe" })

Wrapping deno with strace -f I can see…

deno err <- "[pid    30] write(4, \"\\1\\0\\0\\0\\0\\0\\0\\0\", 8 <unfinished ...>\n"
deno err <- "[pid    29] futex(0x7d0704c006b0, FUTEX_WAIT_PRIVATE, 1, {tv_sec=9, tv_nsec=999999339} <unfinished ...>\n"
deno err <- "[pid     5] <... epoll_wait resumed>[{events=EPOLLIN, data={u32=0, u64=0}}], 1024, 80701) = 1\n"
deno err <- "[pid    30] <... write resumed>)        = 8\n"
deno err <- "[pid     5] epoll_wait(3,  <unfinished ...>\n"
deno err <- "[pid    30] futex(0x7d07048006b0, FUTEX_WAIT_PRIVATE, 1, {tv_sec=9, tv_nsec=999999379} <unfinished ...>\n"
deno err <- "[pid    23] <... clock_nanosleep resumed>0x7d07079ff060) = 0\n"
deno err <- "[pid    23] kill(30076, 0)              = -1 ESRCH (No such process)\n"
deno err <- "[pid    23] sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0\n"
deno err <- "[pid    23] munmap(0x7d071e5ae000, 12288) = 0\n"
deno err <- "[pid    23] exit_group(1 <unfinished ...>\n"

that it's actually trying to signal the editor process o_0 and quietly exiting when that fails. :|

I've just "solved" this for myself by writing an LD_PRELOAD hook that eats the error :laughing:

#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <dlfcn.h>
static int (*actually_kill)(pid_t pid, int sig) = NULL;
int kill(pid_t pid, int sig) {
    if (!actually_kill) actually_kill = dlsym(RTLD_NEXT, "kill");
    int res = actually_kill(pid, sig);
    if (res < 0 && errno == ESRCH) return 0; // lol whatevs
    return res;
}

but someone with more time on their hands should investigate where the kill actually comes from.

Ravenstine commented 8 months ago

@valpackett Indeed, the LD_PRELOAD trick successfully works around the issue for me in Docker. Thanks for suggesting it! Doesn't seem to have any negative side effects.