vi / websocat

Command-line client for WebSockets, like netcat (or curl) for ws:// with advanced socat-like functions
MIT License
6.74k stars 261 forks source link

Server mode does not respond to Ctrl-C (until it handles any client) #96

Open ratijas opened 3 years ago

ratijas commented 3 years ago

Summary

Websocat does not respond to Ctrl-C in docker. It can't be stopped — only killing its container helps.

~It claims to start listening on port, but the not even telnet is able to connect to that port, nor it is listed in lsof -i output. (yep, I run docker with -p 1234:1234 alright).~ (See partial solution below)

Solution (partial)

Part of my problem is that I blindly copied websocat -s 1234 example from the README. Indeed, websocat is up and running, but listening on container-local interface 127.0.0.1 which is not visible on the host machine. But Ctrl-C is not working yet.

$ docker run --rm -p 1234:1234 solsson/websocat -s 1234
Listening on ws://127.0.0.1:1234/
^C^C^C

-- no reaction whatsoever.

Once I run docker exec inside a running websocat container, I am able to connect to a server from an instance of websocat client:

$ docker exec -it 97c7b0415b7c /bin/bash
root@97c7b0415b7c:/# websocat ws://127.0.0.1:1234/
hello

-- and it works as expected: server prints the same message on its terminal.

After handling even a single message from a client, server starts responding to Ctrl-C.

$ docker run --rm -p 1234:1234 solsson/websocat -s 1234
Listening on ws://127.0.0.1:1234/
^C^C^Chello
^C
$ # OK, we are cool
vi commented 3 years ago

Reproduced locally, also got to the reason it works that way.

vi commented 3 years ago

Workaround: start websocat as non-init process:

$ docker run -it --rm -p 1234:1234 --entrypoint=/bin/bash solsson/websocat -c "(websocat -s 1234)"
ratijas commented 3 years ago

That sounds logical from the implementation point of view, but practically most tools in docker used to terminate on Ctrl-C regardless of being init or not.

vi commented 3 years ago

Maybe I can make this signal handler default, not dependent on necessity of running pre-termination procedures.

most tools in docker used to terminate on Ctrl-C regardless of being init or not

I tried the simplest one (/bin/cat) and it seems to also fail to terminate on Ctrl+C:

# docker run -it --rm -p 1234:1234 --entrypoint=/bin/cat solsson/websocat
sdf
sdf
sdf
eee
eee
^C

sdfsf
sdfsf
rrrr
rrrr
^C

^C

Can you list some of those other tools? Maybe there is some trick to make it straighforward.

ratijas commented 3 years ago

Hmm... I don't remember ever running into that. Maybe I was just lucky until today.

But what about --entrypoint=/bin/bash? I guess it works only because bash handles SIGCHLD when child dies?

Init processes don't typically terminate on signals

And what exactly means that line of your? Is there some code like if pid != 1 { handle_sigint() } somewhere in... runtime's default handler? operating system kernel?

vi commented 3 years ago

But what about --entrypoint=/bin/bash?

It makes websocat stopping being init process (instead bash becomes one), so signals work as usual.

With proper ENTRYPOINT script it can be made appear as if you are starting Websocat directly (without those -c "(websocat ... )").

I guess it works only because bash handles SIGCHLD when child dies?

I don't know how it is related to SIGCHLDs

And what exactly means that line of your? Is there some code like if pid != 1 { handle_sigint() } somewhere in... runtime's default handler?

No

operating system kernel?

Yes.

Maybe it's not a special code, just alternative default dispositions for unhandled signals.

ratijas commented 3 years ago

How interesting. Never thought docker would introduce that kind of problems.

I guess it works only because bash handles SIGCHLD when child dies?

I don't know how it is related to SIGCHLDs

I mean, child handles Ctrl-C and thus exits. When that happens, parent process (bash) receives a signal SIGCHLD, and since that was its only child process, it also exits while propagating exit status. At least, that's my understanding of things.

vi commented 3 years ago

When that happens, parent process (bash) receives a signal SIGCHLD, and since that was its only child process, it also exits while propagating exit status.

Something like that. Bash would just act as a wrapper.

ratijas commented 3 years ago

Oh wait oh fck I think I know why.

There are many ways to specify both ENTRYPOINT and CMD in Dockerfile, docker-compose and command line. Some of them (namely, without formal JSON array notation) involve automatic wrapping the whole script in sh -c, known as shell form. And that might be the reason some tools in docker handle Ctrl-C out-of-the-box as they should.

https://docs.docker.com/engine/reference/builder/#entrypoint

ratijas commented 3 years ago

Another interesting and related stuff found: STOPSIGNAL instruction in Dockerfile. Used by nginx, for example.

https://docs.docker.com/engine/reference/builder/#stopsignal