nginx的event loop与内存池深度耦合:event loop决定执行时机,内存池约束执行上下文与资源生命周期;所有事件回调及其数据必须绑定到有效内存池(如r->pool、c->pool或cycle->pool),池销毁后相关指针立即失效,跨池访问将导致use-after-free。

Nginx 的 Event Loop 和内存池不是并行协作的两个模块,而是深度耦合、彼此约束的统一机制:Event Loop 负责“什么时候执行”,内存池决定“在哪块内存上执行、执行完后资源怎么收尾”。所有事件回调(handler)的生命周期、上下文数据的生存期、甚至 handler 本身的注册结构体(ngx_event_t),都必须依附于某个明确的内存池作用域。
事件回调必须绑定到有效内存池的上下文中
每个 ngx_event_t 结构体本身通常分配在 connection 或 request 的内存池中;它的 data 字段常指向 ngx_connection_t* 或 ngx_http_request_t*,而这些结构体本身也由对应池(c->pool 或 r->pool)管理。这意味着:
- 若事件 handler 在 request 处理中途注册(例如读取完 header 后为 body 注册
ngx_http_read_client_request_body),该 handler 及其依赖的上下文(如ngx_http_request_body_t)必须从r->pool分配 - 一旦
ngx_http_finalize_request调用并销毁r->pool,所有指向该池内内存的指针立即失效——即使事件尚未就绪,也不能再访问 - 监听套接字的 accept 事件 handler(
ngx_event_accept)绑定在 cycle 级或 listen socket 所属连接池中,因此可长期存在,但其创建的新连接仍需各自独立的池
事件循环不主动管理内存,只依赖池的自动释放边界
Event Loop(ngx_process_events_and_timers 主循环)本身不 new/delete、不 malloc/free,也不跟踪任何变量生命周期。它只是同步调用 ev->handler(ev)。能否安全执行,完全取决于 handler 内部是否仍在有效内存范围内:
- 如果 handler 访问了已随请求销毁的
r->headers_in,就是典型的 use-after-free,会崩溃或返回乱码 - 子请求(subrequest)有自己的
r->pool,其事件 handler 只能访问本子请求池内的数据,不能跨池引用父请求的临时 buffer - 定时器(
ngx_add_timer)若要访问 request 数据,必须确保定时器结构体(ngx_timer_t)和所持上下文都分配在同一个r->pool中,且不能在 request 结束前被 worker 循环之外的路径提前触发
连接级与请求级池共同支撑事件状态机流转
Nginx 将一次 TCP 连接的完整生命周期拆解为多个逻辑阶段,不同阶段使用不同粒度的内存池:
- TCP 连接建立后,分配
c->pool,用于存放连接相关上下文、SSL session、读写 buffer 等——只要连接未关闭,这块池就一直有效 - 当解析出完整 HTTP 请求头,Nginx 创建新 request(
ngx_http_init_request),分配专属的r->pool,所有 rewrite/access/content 阶段的中间数据、ctx、临时字符串均从此池来 - 响应发送完毕、连接进入 keepalive 或关闭时:
→ 若复用连接,r->pool销毁,但c->pool保留,等待下一个 request
→ 若关闭连接,c->pool一并销毁
跨请求/跨连接共享数据必须绕开 request pool
如果某个事件 handler 需要读写全局状态(如限流计数器、缓存 key 查询结果),绝不能把该状态存进 r->pool:
- 应分配在
cycle->pool(主配置生命周期)或更稳妥的共享内存(shm),由ngx_shared_memory_add显式创建 - 定时器或后台清理任务若需长期运行,其数据结构必须分配在
cycle->pool或使用ngx_alloc堆内存,并自行ngx_free - 常见错误:在 content handler 中把 Lua VM state 存入
r->pool,然后用 cosocket 异步发请求——回调触发时 request 已结束,池被销毁,VM 指针悬空











