使用Django+Celery+Channels时遭遇filedescriptor out of range in select()错误
filedescriptor out of range in select() 错误排查与修复 这个问题我在维护类似的Django+Celery+Channels项目时也碰到过,核心原因是Python 3.6默认使用的SelectSelector依赖系统select调用,而select只能处理数值小于1024的文件描述符(FD)。当Celery worker长时间运行积累过多FD,或者async_to_sync处理异步上下文时FD未正确回收,就会触发这个偶发错误。结合你的环境,给你几个可行的解决办法:
1. 切换到无FD限制的选择器(推荐)
直接替换asyncio的默认选择器为PollSelector或EpollSelector(Linux专属,性能更好),突破1024的FD限制。
在Celery worker的启动入口(比如celery.py或项目初始化文件)添加以下代码:
import asyncio from selectors import PollSelector # Linux下可以换成EpollSelector # 设置事件循环策略并替换选择器 asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy()) loop = asyncio.get_event_loop() loop.set_selector(PollSelector())
这段代码要保证在Celery worker启动前执行,确保所有异步上下文都使用新的选择器。
2. 优化Celery Worker的资源回收
Celery worker长时间运行可能出现FD泄漏,通过配置让worker处理一定任务后自动重启,避免FD持续积累:
在你的Celery配置文件中添加:
app.conf.update( worker_max_tasks_per_child=1000, # 每个worker处理1000个任务后自动重启 worker_disable_rate_limits=True, # 禁用不必要的速率限制,减少FD占用 )
根据业务量调整worker_max_tasks_per_child的数值,比如任务耗时短可以设为2000,耗时久则设为500。
3. 规范async_to_sync的使用方式
检查你调用async_to_sync的异步函数,确保所有IO资源(数据库连接、Redis连接、文件句柄等)都被正确关闭。同时避免在Celery任务中手动创建事件循环——async_to_sync会自动处理事件循环的创建与销毁,手动创建反而可能导致FD泄漏。
错误示例(要避免):
# 不要在Celery任务里手动创建循环 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) async_to_sync(...)()
4. 升级Python版本(长远根治方案)
Python 3.7及以上版本默认使用PollSelector(跨平台)或EpollSelector(Linux),彻底移除了select的1024 FD限制。如果业务允许,升级到Python 3.8+不仅能解决这个问题,还能获得更好的性能和异步特性支持。
验证方法
修改配置后,你可以用lsof -p <worker_pid>命令查看Celery worker进程的FD数量,确认是否超过1024;同时观察业务运行,看错误是否不再触发。
内容的提问来源于stack exchange,提问作者hardik24

