Skip to content

Instantly share code, notes, and snippets.

@soareschen
Last active August 29, 2015 14:15
Show Gist options
  • Save soareschen/240e49116c7f2632d179 to your computer and use it in GitHub Desktop.
Save soareschen/240e49116c7f2632d179 to your computer and use it in GitHub Desktop.
Zsh scripts leave defunct processes when running under docker exec

Zsh scripts leave defunct processes when running under docker exec

This only happens when the script is running inside a shell spawned by docker exec. When running under bash or under docker run, there is no defunct process.

This bug is first discovered when I run nvm in docker (creationix/nvm#650). It was then discovered that Docker's nsenter did not properly handle the SIGCHLD signal raised from zsh. (docker/libcontainer#369) Even though it is now been fixed by Docker, I am not sure why zsh would cause this bug in the first place.

Steps to reproduce:

# terminal 1
docker run -it --rm --name test-zsh ubuntu:latest /bin/bash
apt-get install zsh curl
curl https://gist.githubusercontent.com/soareschen/240e49116c7f2632d179/raw/0be67acefd8d18fd62bb181998996f9a5772dc64/docker-zsh-test.sh > docker-zsh-test.sh
chmod +x docker-zsh-test.sh
./docker-zsh-test.sh
ps auxf # no defunct process

# terminal 2
docker exec -it test-zsh /bin/zsh
./docker-zsh-test.sh
ps auxf # sed and zsh shown as defunct processes

What Actually Happened?

I have talked to some people at Docker and zsh and find out what actually went wrong.

From my understanding, Linux has a feature called subreaper that can take over the role of init and become the parent of orphaned descendant processes. In this case on top of having different PID namespaces, docker exec is spawning a process called nsenter to act as the subreaper. It is responsible to reap defunct processes by handling SIGCHLD. With it being the subreaper, all orphaned/defunct processes are captured by nsenter instead of propogating to the actual init process inside the container. So current Docker didn't handle SIGCHLD properly and therefore causing all orphaned processes to become defunct.

On the other hand when zsh is processing a script, it will optimize process handling and perform an implicit exec of the final command in the script. So zsh need not hang around waiting for that process to exit, assuming that it's own parent will reap it. (zsh mla). In other words zsh is orphaning all its child processes after finish running the last command. With Docker's subreaper not handling orphaned processes properly, it just make this bug particularly obvious when running zsh scripts.

#!/bin/zsh
# The following zsh shell script would leave defunct processes around
# when running inside docker exec.
#
# Specifically a zsh subshell and the second command sed in the pipeline
# would become defunct.
if [[ "$(echo "the black cat was chased by the brown dog" | sed -e 's/cat/fox/g' | grep fox)" = "" ]]; then
echo "cat"
else
echo "fox"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment