Python中如何解决eventlet的keepalive bug?Google上也搜不到好办法
def process_request(self, sock_params):
# The actual request handling takes place in __init__, so we need to
# set minimum_chunk_size before __init__ executes and we don't want to modify
# class variable
sock, address = sock_params
proto = new(self.protocol)
if self.minimum_chunk_size is not None:
proto.minimum_chunk_size = self.minimum_chunk_size
proto.capitalize_response_headers = self.capitalize_response_headers
try:
proto.__init__(sock, address, self)
except socket.timeout:
# Expected exceptions are not exceptional
sock.close()
# similar to logging "accepted" in server()
self.log.debug('(%s) timed out %r' % (self.pid, address))
BUG 就在上面, 绿色线程抛了一个假的 socket.timeout 错误 这里调用 sock close 无效, 先 shutdown 也没用....
没有有高人知道怎么解决这个 bug
Python中如何解决eventlet的keepalive bug?Google上也搜不到好办法
我理解你遇到了eventlet的keepalive相关的bug。这个问题确实比较棘手,因为eventlet的keepalive机制在某些网络环境下可能会出现问题,特别是在处理长时间连接时。
让我给你一个完整的解决方案。首先,我们需要明确问题的具体表现:通常是在使用eventlet的HTTP客户端或服务器时,keepalive连接没有正确关闭,导致连接泄漏或资源耗尽。
import eventlet
from eventlet.green import socket
from eventlet.green.urllib import request
import time
def fix_keepalive_issue():
"""
解决eventlet keepalive问题的完整方案
"""
# 方案1: 禁用keepalive(最简单直接)
# 对于HTTP客户端,可以设置请求头禁用keepalive
def make_request_without_keepalive(url):
req = request.Request(url)
req.add_header('Connection', 'close') # 明确关闭keepalive
response = request.urlopen(req)
return response.read()
# 方案2: 设置合理的超时时间
# 为socket设置超时,避免连接无限期保持
timeout = 30 # 30秒超时
socket.setdefaulttimeout(timeout)
# 方案3: 使用连接池并手动管理
# 创建自定义的连接池,控制keepalive行为
class ManagedConnectionPool:
def __init__(self, max_size=10, idle_timeout=60):
self.max_size = max_size
self.idle_timeout = idle_timeout
self.pool = []
self.last_used = {}
def get_connection(self, host, port):
# 清理过期连接
self._cleanup_expired()
# 查找可用连接
for i, (h, p, conn) in enumerate(self.pool):
if h == host and p == port:
self.pool.pop(i)
self.last_used[id(conn)] = time.time()
return conn
# 创建新连接
conn = socket.create_connection((host, port))
self.last_used[id(conn)] = time.time()
return conn
def return_connection(self, host, port, conn):
if len(self.pool) < self.max_size:
self.pool.append((host, port, conn))
else:
conn.close()
def _cleanup_expired(self):
current_time = time.time()
expired = []
for i, (host, port, conn) in enumerate(self.pool):
conn_id = id(conn)
if conn_id in self.last_used:
if current_time - self.last_used[conn_id] > self.idle_timeout:
expired.append(i)
# 关闭过期连接
for i in reversed(expired):
host, port, conn = self.pool.pop(i)
conn.close()
if id(conn) in self.last_used:
del self.last_used[id(conn)]
# 方案4: 使用monkey_patch的替代方案
# 如果问题与monkey_patch相关,可以尝试选择性patch
def selective_monkey_patch():
# 只patch必要的模块
eventlet.monkey_patch(socket=True, select=True)
# 避免patch可能导致问题的模块
# eventlet.monkey_patch(all=False) # 明确关闭所有patch
# 方案5: 对于eventlet的webserver,调整服务器配置
def configure_webserver():
from eventlet import wsgi
import eventlet
# 设置服务器参数
server_config = {
'socket_timeout': 30, # socket超时
'keepalive': True, # 是否启用keepalive
'keepalive_timeout': 15, # keepalive超时时间
'log_output': False, # 关闭日志输出,减少干扰
}
# 或者完全禁用keepalive
server_config_no_keepalive = {
'socket_timeout': 30,
'keepalive': False, # 直接禁用
}
return server_config
# 返回配置好的函数和类
return {
'make_request': make_request_without_keepalive,
'ManagedConnectionPool': ManagedConnectionPool,
'selective_monkey_patch': selective_monkey_patch,
'configure_webserver': configure_webserver
}
# 使用示例
if __name__ == "__main__":
# 获取解决方案
solutions = fix_keepalive_issue()
# 示例1: 使用禁用keepalive的请求
try:
# data = solutions['make_request']('http://example.com')
print("禁用keepalive的请求配置完成")
except Exception as e:
print(f"请求失败: {e}")
# 示例2: 使用管理的连接池
pool = solutions['ManagedConnectionPool'](max_size=5, idle_timeout=30)
# 示例3: 选择性monkey patch
solutions['selective_monkey_patch']()
print("Eventlet keepalive问题解决方案已就绪")
# 额外建议:如果上述方案都不行,考虑升级或降级eventlet版本
# 某些版本的eventlet有已知的keepalive问题,可以尝试:
# pip install eventlet==0.30.2 # 或尝试其他稳定版本
这个解决方案提供了几个不同层面的处理方式:
- 最直接的:在HTTP请求中明确添加
Connection: close头部,强制关闭keepalive - 连接管理:实现自己的连接池,控制连接的创建、重用和清理
- 配置调整:合理设置超时时间,避免连接无限期保持
- 选择性patch:只patch必要的模块,减少冲突可能性
实际使用中,你可以根据具体情况选择其中一种或多种组合。如果问题特别严重,可以考虑回退到eventlet的某个稳定版本,或者评估是否真的需要使用eventlet——有时候用原生的asyncio或gevent可能更稳定。
试试看哪种方案对你的场景有效。
今天闲着没事 终于把这个 bug 搞定了…
eventlet 的 HttpProtocol(继承自 python 的 BaseHTTPRequestHandler)在接收数据的时候为了方便
通过 dup 把 socket 复制成了 file 对象
由于 python2.6 的 BaseRequestHandler 没有在 finally 中调用 finish
导致异常发生的时候没有关闭掉 dup 出来的 fd
所以调用了 socket.close 并不能关闭 socket
由于在 windows 环境一直是 2.7 的,导致一直没找到 bug…
真是蛋痛…

