我们在测试代码时,由于需要经常重启服务,经常会发现服务端口被占用。
一般kill掉后台进程就ok了,但是如果服务有启动一些常驻的后台程序,可能也会导致端口不能释放。
在类UNIX系统中,一切被打开的文件、端口被抽象为文件描述符(file descriptor)
从python3.4开始,文件描述符默认是non-inheritable,也就是子进程不会共享文件描述符。
问题
一般为了实现多进程、多线程的webserver,服务端口fd必须设置为继承(set_inheritable),这样才能多进程监听一个端口(配合SO_REUSEPORT)
典型的是使用flask的测试服务器的场景,这里我们写一段代码模拟。
1 | import socket, os |
我们通过lsof -p {pid}
可以看到这两个进程的所有文件描述符
server进程, 可以看到服务端口的fd是41
2
3
4
5
6
7
8
9
10
11COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME
ptpython 6214 cwd DIR 253,0 4096 872946898 /
...
ptpython 6214 0u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 1u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 2u CHR 136,13 0t0 16 /dev/pts/13
ptpython 6214 3r CHR 1,9 0t0 2057 /dev/urandom
ptpython 6214 4u sock 0,7 0t0 58345077 protocol: TCP
ptpython 6214 5u a_inode 0,10 0 8627 [eventpoll]
ptpython 6214 6u unix 0x0000000000000000 0t0 58368029 socket
ptpython 6214 7u unix 0x0000000000000000 0t0 58368030 socket
sleep子进程,也拥有fd=4的文件描述符1
2
3
4
5
6
7COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME
python 18022 cwd DIR 253,0 4096 872946898 /
...
python 18022 0u CHR 136,13 0t0 16 /dev/pts/13
python 18022 1u CHR 136,13 0t0 16 /dev/pts/13
python 18022 2u CHR 136,13 0t0 16 /dev/pts/13
python 18022 4u sock 0,7 0t0 58345077 protocol: TCP
如果server进程退出时,sleep进程没有退出,fd=4对应的端口就被占用了,服务也就不能正常启动了。
解决方法
手动清理
1 | import os |
使用close_fds
使用subprocess库而不是os来启动子程序, 通过close_fds参数关闭多余的文件描述符1
2import subprocess
subprocess.call("python -c 'import time;time.sleep(1000)'", shell=True, close_fds=True)