REditorSupport / languageserver

An implementation of the Language Server Protocol for R
Other
586 stars 94 forks source link

LS exits on startup within a Docker container #25

Closed hifall closed 6 years ago

hifall commented 6 years ago

After I have installed successfully the R language server, I run the following command:

R --quiet --slave -e 'languageserver::run()'

This, I think, is supposed to start the language server. However, it exits in about 1 second without printing any message.

How do I start the language server and keep it alive and accepting input from stdin, like most other language servers do?

My env: Ubuntu 16.04.

Thanks!

randy3k commented 6 years ago

it should stay alive, no idea why it exits immediately. by the way, you shouldn’t call it manually. what editor are you using?

hifall commented 6 years ago

I am using MS Monaco. I need the R LS to stay up so that I can try to connect to it.

How can I view the log, so that I might find why it exits?

randy3k commented 6 years ago

what happens if you run languageserver::run() in R

hifall commented 6 years ago

If I run languageserver::run() in R's REPL, it looks like this:

> languageserver::run()
> 

That is, it completes that command without printing anything.

randy3k commented 6 years ago

How about languageserver::run(debug=T)? Even Better, could you use debugOnce(languageserver::run) to see which line does it jump out.

hifall commented 6 years ago

Here is what it produces:

> languageserver::run(debug=T)
connection type: stdio
exiting
> debugOnce(languageserver::run)
Error in debugOnce(languageserver::run) : 
  could not find function "debugOnce"
> 
hifall commented 6 years ago

Do I need to install anything to use debugOnce? I am very new to R.

randy3k commented 6 years ago

Sorry, I meant debugonce. Nevermind, forget it for now.

How about the following? And what version of R are you using?

inputcon = file("stdin")
open(inputcon, blocking = FALSE)
isOpen(inputcon)

languageserver:::getppid()
hifall commented 6 years ago

My R:

R version 3.4.4 (2018-03-15) -- "Someone to Lean On" Copyright (C) 2018 The R Foundation for Statistical Computing Platform: x86_64-pc-linux-gnu (64-bit)

hifall commented 6 years ago

Running your commands yields:

> inputcon = file("stdin")
> open(inputcon, blocking = FALSE)
> isOpen(inputcon)
[1] TRUE
> languageserver:::getppid()
[1] 1
> 
randy3k commented 6 years ago

Oh I see, languageserver:::getppid() is the reason. For some reasons, getppid doesn't return the parent process id.

hifall commented 6 years ago

Glad you got it.

Now, is there anything I can do on my end to get around this issue, or do I have to wait for some fix in the future?

randy3k commented 6 years ago

I actually don't have any clues how it could happen. getppid should return the parent process id (unless it is an orphan process in which case it returns 1). But clearly you are running R on a shell and it is not an orphan.

On my Ubuntu machine,

> languageserver:::getppid()
[1] 6093
hifall commented 6 years ago

Weird. I am running this in Docker/Ubuntu 16.04. But I don't think that's the cause.

randy3k commented 6 years ago

I have just fired up a rocker/tidyverse and it returns

> languageserver:::getppid()
[1] 156
>
randy3k commented 6 years ago
docker run --name foobar -d rocker/tidyverse:3.4.4
docker exec -it foobar /bin/bash

# in the docker machine, open R and install languageserver
root@38a2dbc752d4:/# R
> install.packages("languageserver")
> languageserver:::getppid()
hifall commented 6 years ago

Interesting. I run this in a uber-complex container with tons of software packages installed, one of which is R. Therefore, R is not the only player here.

Although unlikely, but it's not entirely impossible caused by some external package configs.

I would have to remove these packages gradually in order to find out what is causing this issue, if at all.

hifall commented 6 years ago

There is much discussion on SO on why getppid returns 1, like these: https://stackoverflow.com/questions/15183427/getpid-and-getppid-return-two-different-values https://stackoverflow.com/questions/16078188/why-getppid-from-the-child-return-1

Not sure if these are relevant to the issue in discussion here.

randy3k commented 6 years ago

Do you open R via a shell (e.g. Bash) at all?

Checking getppid is important because it makes sure the R process will terminate itself. Some language servers don't check such and sometimes may leave a process running in the background after the editor is closed.

hifall commented 6 years ago

The way I run it:

Start the container: docker run -it --entrypoint /bin/bash myimage Then, run R in bash inside the container: R Then, enter: languageserver:::getppid()

So yes, I do open R via a Bash.

randy3k commented 6 years ago

how about check it using ps?

> ps -o pid,ppid,cmd | grep /usr/local/bin/R
539   289 grep /usr/local/bin/R
hifall commented 6 years ago

"Some language servers don't check such and sometimes may leave a process running in the background after the editor is closed."

As far as my experience with multiple language servers (LS) is concerned, a common pattern in their behavior is that if the editor disconnects, the LS becomes idle. And if an LS stays idle over some threshold (a couple minutes, a precise number doesn't matter here), it kills itself. So there will be no zombie process if the LS is designed like this.

hifall commented 6 years ago
root@851cf02ada5b:/# ps -o pid,ppid,cmd | grep /usr/local/bin/R
   66     1 grep --color=auto /usr/local/bin/R
randy3k commented 6 years ago

But still, checking the parent's state explicitly should be better than waiting for a couple minutes (sadly, not for your use case).

So it is not an issue of languageserver or R. There is something fishy on your system..

hifall commented 6 years ago

I might check the container later. Thanks for your help!

If you have new thoughts later, feel free to share:)

hifall commented 6 years ago

Okay, I trimmed down the Dockerfile to a minimal to create the image. Now it looks like:

FROM ubuntu:16.04
#Common deps
RUN apt-get update && apt-get -y install curl xz-utils vim unzip libssl-dev libreadline-dev zlib1g-dev
# Refer to: https://www.r-bloggers.com/how-to-install-r-ubuntu-16-04-xenial/.
RUN apt-get update && apt-get install -y libcurl4-openssl-dev && \
    echo "deb http://cran.rstudio.com/bin/linux/ubuntu xenial/" | tee -a /etc/apt/sources.list && \
    gpg --keyserver keyserver.ubuntu.com --recv-key E084DAB9 && \
    gpg -a --export E084DAB9 | apt-key add - && \
    apt-get update && \
    apt-get install -y r-base r-base-dev && \
    R --quiet --slave -e 'install.packages("languageserver")'

But still, running languageserver:::getppid() gives:

> languageserver:::getppid()
[1] 1
> 

So if indeed this getppid thing is the issue, this issue can only come from the settings above, R, or the R language server, nothing else.

randy3k commented 6 years ago

Thanks for the file, I will check it out later. 👍

randy3k commented 6 years ago

I cannot reproduce with your Dockerfile....

docker build -t "randy3k:dockerfile" .
docker run --name foobar -dit randy3k:dockerfile
docker exec -it foobar /bin/bash
root@50277a9e326e:/# R
> languageserver:::getppid()
[1] 27

where 27 is the pid of bash

root@50277a9e326e:/# ps -o pid,ppid,cmd | grep bash
   27     0 /bin/bash
   51    27 grep --color=auto bash
hifall commented 6 years ago

This is getting more weird. The only remaining movable parts in the whole system I can think of are the OS and Docker.

My OS: Ubuntu 16.04 LTS. My Docker: 18.03.1-ce, build 9ee9f40.

hifall commented 6 years ago

Okay, there is actually one more thing: the way you run it is different than mine. I just executed your commands with complete fidelity and it seemed to give me a different result:

> languageserver:::getppid()
[1] 36
hifall commented 6 years ago

I ran it like below before, which is different than yours:

docker run -it --entrypoint /bin/bash myimage
R
> languageserver:::getppid()
[1] 1
> 

Conclusion: the way I ran it causes getppid to always return 1.

randy3k commented 6 years ago

That's interesting. I actually have never used the --entrypoint argument.

check https://hackernoon.com/the-curious-case-of-pid-namespaces-1ce86b6bc900

It seems that you could start your container with --init.

hifall commented 6 years ago

Thanks. Running with --init does seem to keep the language server running from command line.

thomasjm commented 6 years ago

Hi @randy3k -- I'm running into this issue too, and I think that languageserver refusing to run as a child of PID 1 is kind of a misfeature.

Consider the scenario of a language server client running in a Docker container as the Docker CMD, so it's running as PID 1. As soon as the client tries to start up the R languageserver the server exits immediately.

I've been integrating with a bunch of language servers lately and I've never encountered one before that behaves this way. IMO if an editor fails to clean up its child processes then that is a bug in the editor--it's not the job of the child process to try to detect this. Any chance this behavior could be changed?

randy3k commented 6 years ago

IMO if an editor fails to clean up its child processes then that is a bug in the editor--it's not the job of the child process to try to detect this

I agree. However, I still think that it is beneficial to check if the process becomes an orphan. Provided that there is a workaround with the --init option, I personally don't have much incentive to change the current behavior. With that said, I am open to any contribution allowing an option to control this behavior.

thomasjm commented 6 years ago

Hmm, let me try to persuade you that this is not beneficial but is instead bad non-standard behavior...

If you think about it, just about any Linux process could potentially be started by a parent process and then orphaned. But doing a check like this for PPID 1 is not standard practice in general, because the convention is that it's the parent's job to take care of cleaning up orphans.

This wouldn't be a big deal, except we live in a world where Docker is common and so running with PPID 1 is not unusual.

So all of a sudden, I can't wrap up a Docker app (say, an editor + an R language server) without completely changing my init system because this package is being "helpful." Yes, a workaround exists, but it adds a tiny amount of hassle to anyone who wants to use this package via Docker (i.e. if I want to distribute my Dockerized app then I have to remind the end user to pass --init or else the language server will refuse to run).

In conclusion: non-standard behavior is bad, even when it's trying to be helpful, and the "right thing" would be to just remove the check. What do you think?

randy3k commented 6 years ago

Perhaps we should disable the check specifically for docker? It seems that only docker users will want to disable it.

thomasjm commented 6 years ago

Well, that would be putting a hack on top of a hack, but it would be an improvement :). However, I don't think there exists a satisfactory way to test if you're inside a Docker container, as you can see from the long list of messy answers here.

Can I ask what editor you were using that was orphaning the process?

randy3k commented 6 years ago

Thx for the info. I’ll take a look later. It is Sublime Text.