Valentin Nechayev
2013-09-17 15:44:26 UTC
Столкнулись со следующим эффектом на Linux и не знаю, как это
объяснить. То, что мы попали в область, не описанную стандартами,
понятно, но его конкретная реализация поведения, мягко говоря,
удивляет.
Серверная сторона принимает TCP соединение и в одной из ниток делает
ему read(). По получению SIGTERM, отдельная нитка делает close() всем
известным сокетам. (Всё это внутри библиотеки.)
С этого момента первая нитка остаётся в read() до получения любых
данных по сокету (включая закрытие от клиента), после чего происходит
это самое закрытие (дескриптор больше не валиден). Самое интересное,
что netstat показывает сокет как ESTABLISHED, но никому не
принадлежащий!
Тестовая программа:
===
#!/usr/bin/env python
import socket
import threading
import time
import os
PORT = 40900
def doClient():
cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cln.connect(('127.0.0.1', 40900))
print 'Client is connected'
time.sleep(60)
print 'Client is closing'
cln.close()
print 'Client is closed'
def doServer(st):
time.sleep(1)
cancel_thread = threading.Thread(target = doCancel, name = 'canceller',
args = (st,))
cancel_thread.daemon = True
cancel_thread.start()
try:
r = st.recv(65536)
print 'doServer: r=%r' % (r,)
except Exception as e:
print 'doServer: exception: %r' % (e,)
def doCancel(st):
print 'doCancel'
time.sleep(1)
os.close(st.fileno())
def doDaemon():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('', 40900))
srv.listen(5)
while True:
s2, addr = srv.accept()
print 'Accepted connection from %r' % (addr,)
st = threading.Thread(target = doServer, name = 'server',
args = (s2,))
st.daemon = True
st.start()
if __name__ == '__main__':
dt = threading.Thread(target = doDaemon, name = 'daemon')
dt.daemon = True
dt.start()
time.sleep(1)
ct = threading.Thread(target = doClient, name = 'client')
ct.daemon = True
ct.start()
while True:
os.system('netstat -antp | fgrep 40900')
time.sleep(3)
===
Образец лога:
===
Script started on Tue Sep 17 18:27:12 2013
Client is connectedAccepted connection from ('127.0.0.1', 34721)
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED 32024/python
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
doCancel
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED -
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED -
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
[... так не меняется до закрытия со стороны клиента ...]
Client is closing
Client is closed
doServer: r=''
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 TIME_WAIT -
===
Hаша задача - обеспечить работу завершения серверного процесса с
минимальными деструктивными последствиями для кода:) Вопрос о смене
алгоритма закрытия - за пределами данной темы и будет решаться
отдельно. Hо как можно минимально деструктивно для кода (с учётом
того, что правки надо посылать в апстрим) решить проблему?
Пока что видится один вариант - заменить read() обычный на poll() для
проверки читаемости-или-закрытия с последующим read() при успехе. Hо
Posix обещает POLLHUP при закрытии сокета со стороны, а Linux - нет
(по крайней мере по ману).
--netch--
объяснить. То, что мы попали в область, не описанную стандартами,
понятно, но его конкретная реализация поведения, мягко говоря,
удивляет.
Серверная сторона принимает TCP соединение и в одной из ниток делает
ему read(). По получению SIGTERM, отдельная нитка делает close() всем
известным сокетам. (Всё это внутри библиотеки.)
С этого момента первая нитка остаётся в read() до получения любых
данных по сокету (включая закрытие от клиента), после чего происходит
это самое закрытие (дескриптор больше не валиден). Самое интересное,
что netstat показывает сокет как ESTABLISHED, но никому не
принадлежащий!
Тестовая программа:
===
#!/usr/bin/env python
import socket
import threading
import time
import os
PORT = 40900
def doClient():
cln = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cln.connect(('127.0.0.1', 40900))
print 'Client is connected'
time.sleep(60)
print 'Client is closing'
cln.close()
print 'Client is closed'
def doServer(st):
time.sleep(1)
cancel_thread = threading.Thread(target = doCancel, name = 'canceller',
args = (st,))
cancel_thread.daemon = True
cancel_thread.start()
try:
r = st.recv(65536)
print 'doServer: r=%r' % (r,)
except Exception as e:
print 'doServer: exception: %r' % (e,)
def doCancel(st):
print 'doCancel'
time.sleep(1)
os.close(st.fileno())
def doDaemon():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('', 40900))
srv.listen(5)
while True:
s2, addr = srv.accept()
print 'Accepted connection from %r' % (addr,)
st = threading.Thread(target = doServer, name = 'server',
args = (s2,))
st.daemon = True
st.start()
if __name__ == '__main__':
dt = threading.Thread(target = doDaemon, name = 'daemon')
dt.daemon = True
dt.start()
time.sleep(1)
ct = threading.Thread(target = doClient, name = 'client')
ct.daemon = True
ct.start()
while True:
os.system('netstat -antp | fgrep 40900')
time.sleep(3)
===
Образец лога:
===
Script started on Tue Sep 17 18:27:12 2013
Client is connectedAccepted connection from ('127.0.0.1', 34721)
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED 32024/python
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
doCancel
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED -
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:40900 127.0.0.1:34721 ESTABLISHED -
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 ESTABLISHED 32024/python
[... так не меняется до закрытия со стороны клиента ...]
Client is closing
Client is closed
doServer: r=''
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:40900 0.0.0.0:* LISTEN 32024/python
tcp 0 0 127.0.0.1:34721 127.0.0.1:40900 TIME_WAIT -
===
Hаша задача - обеспечить работу завершения серверного процесса с
минимальными деструктивными последствиями для кода:) Вопрос о смене
алгоритма закрытия - за пределами данной темы и будет решаться
отдельно. Hо как можно минимально деструктивно для кода (с учётом
того, что правки надо посылать в апстрим) решить проблему?
Пока что видится один вариант - заменить read() обычный на poll() для
проверки читаемости-или-закрытия с последующим read() при успехе. Hо
Posix обещает POLLHUP при закрытии сокета со стороны, а Linux - нет
(по крайней мере по ману).
--netch--