google / gvisor

Application Kernel for Containers

Home Page:https://gvisor.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Parent process is not notified about exited child stdin being closed

p12tic opened this issue · comments

Description

The minimized reproduction code below hangs on gVisor. It creates a child process via fork+exec and waits for it to exit including any pending IO operations. On runc the following is returned to epoll_wait of the parent process upon child exit (strace output):

<... epoll_wait resumed>[{events=EPOLLHUP, data={u32=10, u64=10}}, {events=EPOLLHUP, data={u32=8, u64=140733193388040}}, {events=EPOLLERR, data={u32=7, u64=140733193388039}}], 5, -1) = 3

epoll is setup as follows:

epoll_ctl(3, EPOLL_CTL_ADD, 7, {events=EPOLLIN, data={u32=7, u64=140733193388039}}  
epoll_ctl(3, EPOLL_CTL_ADD, 8, {events=EPOLLIN, data={u32=8, u64=140733193388040}}
epoll_ctl(3, EPOLL_CTL_ADD, 10, {events=EPOLLIN, data={u32=10, u64=10}}

Whereas on gVisor the following is observed:

<... epoll_wait resumed>[{events=EPOLLHUP, data={u32=8, u64=140664473911304}}, {events=EPOLLHUP, data={u32=10, u64=10}}], 5, -1) = 2

epoll is setup as follows:

epoll_ctl(3, EPOLL_CTL_ADD, 7, {events=EPOLLIN, data={u32=7, u64=140664473911303}}
epoll_ctl(3, EPOLL_CTL_ADD, 8, {events=EPOLLIN, data={u32=8, u64=140664473911304}}
epoll_ctl(3, EPOLL_CTL_ADD, 10, {events=EPOLLIN, data={u32=10, u64=10}}

The 7, 8, 10 file descriptors refer to pipes to stdin, stdout and stderr of the child process respectively.

As can be seen, on real Linux EPOLLERR is returned for stdin pipe upon child process exit whereas on gVisor this is absent.

Steps to reproduce

sudo podman run -it --runtime=runsc debian:bookworm /bin/bash

In the container:

apt update
apt install -y python3-twisted nano strace
nano bug.py
# <... paste the code snippet below into the file >

# reproduce bug
python3 bug.py

# or, to observe strace output described above:
strace -f python3 bug.py

Test code in Python:


import sys
from twisted.internet import reactor
from twisted.internet import protocol


class LocalPP(protocol.ProcessProtocol):
    def processEnded(self, status):
        print(f"END: {status}")
        super().processEnded(status)
        reactor.stop()


def main():
    cmd = [sys.executable, "-c", "import sys; sys.exit(1)"]
    reactor.spawnProcess(LocalPP(), cmd[0], cmd)
    reactor.run()

if __name__ == '__main__':
    main()

runsc version

runsc version release-20240212.0
spec: 1.1.0-rc.1

docker version (if using docker)

Client:       Podman Engine
Version:      4.7.2
API Version:  4.7.2
Go Version:   go1.21.3
Built:        Thu Jan  1 03:00:00 1970
OS/Arch:      linux/amd64

uname

Linux abcd 6.1.0-15-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.66-1 (2023-12-09) x86_64 GNU/Linux

kubectl (if using Kubernetes)

No response

repo state (if built from source)

No response

runsc debug logs (if available)

No response

epoll_ctl(3, EPOLL_CTL_ADD, 7, {events=EPOLLIN, data={u32=7, u64=140733193388039}}

It is strange that stdin (write-only fd) is polled with EPOLLIN.

Awesome, thanks for a quick turnaround for this bug.

@avagin

It is strange that stdin (write-only fd) is polled with EPOLLIN.

The Twisted framework that exposed this bug has some workarounds for bugs in Linux going back to v2.6.11. I wouldn't be surprised if this behavior was some kind of workaround that has never been touched since then.