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上也搜不到好办法

2 回复

我理解你遇到了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  # 或尝试其他稳定版本

这个解决方案提供了几个不同层面的处理方式:

  1. 最直接的:在HTTP请求中明确添加Connection: close头部,强制关闭keepalive
  2. 连接管理:实现自己的连接池,控制连接的创建、重用和清理
  3. 配置调整:合理设置超时时间,避免连接无限期保持
  4. 选择性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…
真是蛋痛…

回到顶部