Python Gevent.monkey.patch_全部中断依赖于socket.shutdown()的代码
我目前正在为现有的django项目添加对gevent socketio的支持。我发现gevent.monkey.patch_all()调用破坏了负责从套接字接收数据的线程的取消机制,我们现在将调用类SocketReadThread SocketReadThread非常简单,它在阻塞套接字上调用recv()。当它接收到数据时,会对其进行处理并再次调用recv()。当出现异常或recv()返回0字节时,线程停止运行,就像在socket.stop\u readthread()中调用SocketReadThread.stop\u reading()时一样 当gevent.monkey.patch_all()替换默认套接字实现时,就会出现问题。我没有很好地关闭,而是出现以下异常: error: [Errno 9] File descriptor was closed in another greenlet 错误:[Errno 9]文件描述符已在另一个greenlet中关闭 我假设这是因为gevent使我的套接字不阻塞以发挥其魔力。这意味着,当我调用socket.shutdown(socket.shuth\RDWR)时,正在为monkey patchedsocket.recv调用执行工作的greenlet尝试从关闭的文件描述符读取 我编写了一个示例来隔离此问题:Python Gevent.monkey.patch_全部中断依赖于socket.shutdown()的代码,python,sockets,gevent,Python,Sockets,Gevent,我目前正在为现有的django项目添加对gevent socketio的支持。我发现gevent.monkey.patch_all()调用破坏了负责从套接字接收数据的线程的取消机制,我们现在将调用类SocketReadThread SocketReadThread非常简单,它在阻塞套接字上调用recv()。当它接收到数据时,会对其进行处理并再次调用recv()。当出现异常或recv()返回0字节时,线程停止运行,就像在socket.stop\u readthread()中调用SocketRead
from gevent import monkey
monkey.patch_all()
import socket
import sys
import threading
import time
class SocketReadThread(threading.Thread):
def __init__(self, socket):
super(SocketReadThread, self).__init__()
self._socket = socket
def run(self):
connected = True
while connected:
try:
print "calling socket.recv"
data = self._socket.recv(1024)
if (len(data) < 1):
print "received nothing, assuming socket shutdown"
connected = False
else :
print "Recieved something: {}".format(data)
except socket.timeout as e:
print "Socket timeout: {}".format(e)
connected = false
except :
ex = sys.exc_info()[1]
print "Unexpected exception occurrred: {}".format(str(ex))
raise ex
def stop_reading(self):
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
if __name__ == '__main__':
sock = socket.socket()
sock.connect(('127.0.0.1', 4242))
st = SocketReadThread(sock)
st.start()
time.sleep(3)
st.stop_reading()
st.join()
来自gevent导入monkey的
猴子
导入套接字
导入系统
导入线程
导入时间
SocketReadThread类(threading.Thread):
def uuu init uuuu(自我,套接字):
超级(SocketReadThread,self)。\uuuu init\uuuu()
self.\u插座=插座
def运行(自):
已连接=真
连接时:
尝试:
打印“calling socket.recv”
数据=self.\u socket.recv(1024)
如果(len(数据)<1):
打印“未收到任何内容,假设套接字关闭”
已连接=错误
其他:
打印“收到某物:{}”。格式(数据)
除了socket.timeout作为e:
打印“套接字超时:{}”。格式(e)
已连接=错误
除:
ex=sys.exc_info()[1]
打印“意外异常发生:{}”。格式(str(ex))
加薪
def停止_读数(自):
自动关闭插座(插座关闭)
self.\u socket.close()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
sock=socket.socket()
插座连接(('127.0.0.1',4242))
st=短袜头螺纹(短袜)
圣斯特朗
时间。睡眠(3)
圣斯托普雷丁(st.stop_reading)
圣约
如果您打开一个终端,运行nc-lp 4242&(给这个程序一些连接的东西),然后运行这个程序,您将看到上面提到的异常。如果删除对monkey.patch_all()的调用,您将看到它工作正常
我的问题是:如何支持取消SocketReadThread,以一种可以使用或不使用gevent monkey补丁的方式,并且不需要使用会使取消速度变慢的任意超时(即使用超时调用recv(),并检查条件)我发现有两种不同的解决方法。第一种方法是简单地捕获并抑制异常。这似乎工作正常,因为一个线程关闭套接字以使另一个线程退出阻塞读取是一种常见做法。我不知道也不理解为什么greenlets会抱怨除了调试辅助之外的其他问题。这真的只是一种烦恼 第二种选择是使用self-pipe技巧(快速搜索会产生许多解释)作为唤醒阻塞线程的机制。本质上,我们创建了第二个文件描述符(套接字类似于操作系统的一种文件描述符),用于发出取消信号。然后,我们使用select作为阻塞来等待套接字上的传入数据或取消文件描述符上的取消请求。请参见下面的示例代码
from gevent import monkey
monkey.patch_all()
import os
import select
import socket
import sys
import threading
import time
class SocketReadThread(threading.Thread):
def __init__(self, socket):
super(SocketReadThread, self).__init__()
self._socket = socket
self._socket.setblocking(0)
r, w = os.pipe()
self._cancelpipe_r = os.fdopen(r, 'r')
self._cancelpipe_w = os.fdopen(w, 'w')
def run(self):
connected = True
read_fds = [self._socket, self._cancelpipe_r]
while connected:
print "Calling select"
read_list, write_list, x_list = select.select(read_fds, [], [])
print "Select returned"
if self._cancelpipe_r in read_list :
print "exiting"
self._cleanup()
connected = False
elif self._socket in read_list:
print "calling socket.recv"
data = self._socket.recv(1024)
if (len(data) < 1):
print "received nothing, assuming socket shutdown"
connected = False
self._cleanup()
else :
print "Recieved something: {}".format(data)
def stop_reading(self):
print "writing to pipe"
self._cancelpipe_w.write("\n")
self._cancelpipe_w.flush()
print "joining"
self.join()
print "joined"
def _cleanup(self):
self._cancelpipe_r.close()
self._cancelpipe_w.close()
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
if __name__ == '__main__':
sock = socket.socket()
sock.connect(('127.0.0.1', 4242))
st = SocketReadThread(sock)
st.start()
time.sleep(3)
st.stop_reading()
来自gevent导入monkey的
猴子
导入操作系统
导入选择
导入套接字
导入系统
导入线程
导入时间
SocketReadThread类(threading.Thread):
def uuu init uuuu(自我,套接字):
超级(SocketReadThread,self)。\uuuu init\uuuu()
self.\u插座=插座
自锁插座(0)
r、 w=os.pipe()
self.\u cancelpipe\u r=os.fdopen(r,'r')
self.\u cancelpipe\u w=os.fdopen(w,'w')
def运行(自):
已连接=真
读取\u fds=[self.\u插座,self.\u取消管道\u r]
连接时:
打印“调用选择”
读列表,写列表,x列表=选择。选择(读fds,[],[])
打印“选择返回”
如果读取列表中的self.\u cancelpipe\r:
打印“退出”
self._cleanup()
已连接=错误
只读列表中的elif self.\u套接字:
打印“calling socket.recv”
数据=self.\u socket.recv(1024)
如果(len(数据)<1):
打印“未收到任何内容,假设套接字关闭”
已连接=错误
self._cleanup()
其他:
打印“收到某物:{}”。格式(数据)
def停止_读数(自):
打印“写入管道”
self.\u cancelpipe\u w.write(“\n”)
自冲洗管道
打印“加入”
self.join()
打印“已加入”
def_清理(自):
自我取消管道关闭()
self.\u取消管道w.关闭()
自动关闭插座(插座关闭)
self.\u socket.close()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
sock=socket.socket()
插座连接(('127.0.0.1',4242))
st=短袜头螺纹(短袜)
圣斯特朗
时间。睡眠(3)
圣斯托普雷丁(st.stop_reading)
同样,在运行上述程序之前,请运行netcat-lp4242&到gi