如何从外部检测进程输出流是否关闭
问题描述
假设在Ubuntu机器上有以下Python脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#!/bin/env python3
import sys
import time
try:
i = 1
while True:
print(i)
i += 1
except Exception as e:
sys.stderr.write(f"We caught an exception {e!r}\n")
sys.stderr.flush()
while True:
sys.stderr.write("Sleeping for a minute\n")
sys.stderr.flush()
time.sleep(60)
|
使用以下命令运行:
1
|
./some_test.py | head -n 10
|
输出结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
We caught an exception BrokenPipeError(32, 'Broken pipe')
Sleeping for a minute
Sleeping for a minute
...
|
进程不会停止。问题是:从外部能否看出输出流已关闭?
诊断分析
使用lsof检查进程文件描述符:
输出显示:
1
2
3
4
5
6
7
|
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 28466 root cwd DIR 252,3 4096 132 /root
python3 28466 root rtd DIR 252,3 4096 128 /
python3 28466 root txt REG 252,3 5904904 9540 /usr/bin/python3.10
python3 28466 root 0u CHR 136,3 0t0 6 /dev/pts/3
python3 28466 root 1w FIFO 0,13 0t0 110475 pipe
python3 28466 root 2u CHR 136,3 0t0 6 /dev/pts/3
|
检查/proc文件系统:
输出:
1
2
3
4
|
total 0
lrwx------ 1 root root 64 Oct 20 13:44 0 -> /dev/pts/3
l-wx------ 1 root root 64 Oct 20 13:44 1 -> 'pipe:[110475]'
lrwx------ 1 root root 64 Oct 20 13:44 2 -> /dev/pts/3
|
技术解答
流实际上并未关闭:能够尝试写入而不收到EBADF错误就是证明(参见man 2 write)。
在Linux上,对于管道,可以检查所有运行中的进程,看是否有其他进程对同一管道持有打开的文件描述符。如果找到一个,则管道仍可写入而无错误;如果没找到,则要么没有适当权限查看读取进程,要么管道不再可用于写入。
使用lsof的示例:
1
2
3
4
5
6
|
$ sleep 3m | sleep 10 &
[1] 62807 62808
$ lsof -p 62807 -ad 1 +E
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 62807 stephane 1w FIFO 0,14 0t0 806533 pipe 62808,sleep,0r
sleep 62808 stephane 0r FIFO 0,14 0t0 806533 pipe 62807,sleep,1w
|
10秒后运行相同命令:
1
2
3
|
$ lsof -p 62807 -ad 1 +E
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 62807 stephane 1w FIFO 0,14 0t0 806533 pipe
|
此时,lsof无法找到在62807进程的fd 1上的管道的任何其他读取端打开的文件描述符。
不使用lsof的等效方法(zsh代码):
1
2
3
4
5
|
$ sleep 3m | sleep 4m &
[1] 63098 63100
$ ls -ogd /proc/*/fd/*(e['[[ $REPLY -ef /proc/63098/fd/1 ]]'])
l-wx------ 1 64 Oct 20 17:12 /proc/63098/fd/1 -> 'pipe:[810032]'
lr-x------ 1 64 Oct 20 17:12 /proc/63100/fd/0 -> 'pipe:[810032]'
|
这些符号链接的权限指示了每个进程如何打开管道。