客户在使用dmDjango连接池时出现了异常,报错如下

经过检验,能够在复现环境中复现客户“在libdmdpi中发生divide error,随后因SIGFPE退出”的错误,错误码和崩溃位置也一致(libdmdpi-d9dd6fd8.so 内固定偏移0x239EAE的除零停机)。
这是因为当前dmPython_pool连接池的设计是进程内池,一个连接池仅供一个进程使用,进程内多线程并发是安全的(互斥依靠 threading.Condition 实现,但内存锁只对同一进程内的线程有效)
而billiard在linux上创建子进程时默认使用fork方式,导致子进程原样复制来父进程的连接池和池内连接,子进程之间、子进程和父进程之间共用了同一个连接池,且互斥失效:并发使用时会出现协议应答错乱;
如果跨进程使用,某个进程归还连接或关闭连接池后,会导致其他进程的连接状态和服务端的会话状态冲突,驱动主动触发异常(对应的是除零操作,信号是SIGFPE)停机
一旦有一个进程注销了池里的连接或者整个池,服务端的会话就结束了,但其他进程仍持有“可用”状态的连接副本,继续使用或再次注销时。驱动检测到连接状态和服务端会话状态冲突,会主动触发异常停机,在libdmdpi-d9dd6fd8.so上对应的实现就是除零实现停机,所以会出现divide error
而观察市面上常见的关系型数据库(Oracle、PG及其衍生),大多都不保证连接池的多进程安全,均在文档中建议在fork后再建立连接池
所以建议做法是每个进程使用自己的连接池,连接池不要跨进程使用
在本案例中就是先fork建立子进程,不要继承父进程的连接池,在创建子进程之后各自建立各自的连接池。如果继承了父进程的连接池,则各子进程应该丢弃继承的连接池,在首次访问数据库时再各自重建自己的连接池
可以参考demo,在客户给的demo基础上进行了修改(还打开了连接池),经测试不会触发“在libdmdpi中发生divide error,随后因SIGFPE退出”的相同错误
复现测试环境如下:
| 组件 | 版本 |
|---|---|
| 操作系统 | Kylin-Server-V10-SP3-2403 (x86_64) |
| 达梦数据库 | DM8 |
| Python | 3.10 |
| Django | 4.1.13 |
| dmPython | 2.5.38 |
| dmPython_pool | 1.0.0 |
| dmDjango | 3.0.10 |
| libdmdpi | 通过pip安装,libdmdpi-d9dd6fd8.so |
| billiard | 4.2.4 |
| celery | 5.3.6 |
基于复现包dm_celery_core_repro修改,仅改动了两个文件:
# 修复: 子进程内保住继承池对象的引用, 防止 GC 触发 __del__ 注销父进程的连接
_inherited_pools: list = []
@signals.worker_process_init.connect
def worker_process_init_handler(**_kwargs):
# 修复: fork 出的子进程丢弃继承的连接池, 首次访问数据库时在本进程内重建。
# 步骤 1: 借出的连接归还继承池(close 对池化引擎只是还池, 不注销)
# 步骤 2: 池对象先保引用再清空字典(不能调 close: close 会向父进程的
# socket 发注销包, 导致父进程会话失效甚至驱动停机)
……(和原来一样,略过)
from django_dmpython_pool.dmDjango.base import DatabaseWrapper
_inherited_pools.extend(DatabaseWrapper._connection_pools.values())
DatabaseWrapper._connection_pools.clear()
logger.info(
"worker_process_init: inherited pool discarded (kept %d ref(s)), will rebuild per-worker",
len(_inherited_pools),
)
except Exception:
logger.exception("worker_process_init: failed to discard inherited pool")

需要注意,池对象必须先保存引用再清空字典,否则池对象被回收时会触发析构逻辑,向父进程的连接发送注销包,同样会引发崩溃;不能调用pool.close()或close_pool(),效果等同于向父进程的 socket 发送注销包。
用法与原复现包用法一致,在复现包目录下设置运行环境变量后执行一键脚本即可,
cd /tmp/dm_celery_core_repro_pool_fixed
export PATH=/Work/billiard/venv_demo/bin:$PATH
export SECKER_DB_NAME=DAMENG SECKER_DB_USER=SYSDBA SECKER_DB_PASSWORD=Dameng123
export SECKER_DB_HOST=127.0.0.1 SECKER_DB_PORT=5237
export REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASS=dmrepro123
export PYTHONUNBUFFERED=4
sh run_crash_mode.sh
测试条件与原复现包的crash模式一致:父进程在fork每个子进程前预先打开达梦连接、保留任务执行前关闭连接的动作。且已启用连接池。
但建议在fork之后才打开连接,这里只是说明即使在fork前打开连接也能正常处理
修复后运行的判断标准如下(以4个并发、16个任务为例)
[INFO/ForkPoolWorker-1] worker_process_init: Django DB connections closed
[INFO/ForkPoolWorker-1] worker_process_init: inherited pool discarded (kept 1 ref(s)), will rebuild per-worker
[INFO/ForkPoolWorker-1] [Celery_Task] DB probe ok task id=... iteration=0 row=(1,)
[INFO/ForkPoolWorker-1] Task probe.tasks.polling_offline_log_import_check[...] succeeded in 0.0029s
测试结果是4个任务全部入队且全部执行成功,没有出现divide error、-70019、signal退出



| 观察项 | 修复前 | 修复后 |
|---|---|---|
| worker进程 | 被信号8(SIGFPE) 杀死 | 全部正常存活 |
| 内核日志 | divide error @ libdmdpi-d9dd6fd8.so偏移0x239EAE | 无新增异常记录 |
| 主进程日志 | -70019网络通讯失败 | 无异常 |
| 任务执行 | 中断或拿到错乱结果 | 全部执行成功、结果正确 |
| 修复后丢弃继承池的日志 | 无 | 每个 worker 一条 inherited pool discarded |
说明通过“子进程丢弃继承的连接池,在首次访问数据库时再各自重建自己的连接池”的做法,成功在业务侧解决了“在libdmdpi中发生divide error,随后因SIGFPE退出”的问题
链接内的文字是对该链接的简要说明,冒号后面的英文是链接内的原文
_default_context is now initialized with SpawnContext, which routes process creation through popen_spawn_posix.Popen instead of popen_fork.Popen.DatabaseWrapper._connection_pools). When gunicorn (or any pre-forking server) forks worker processes, all children inherit references to the same pool objects — and crucially, the same underlying TCP sockets to PostgreSQL. Multiple workers then read/write the same socket concurrently, corrupting the PostgreSQL wire protocol.[root@localhost dm_celery_core_repro_pool_fixed]# export DM_REPRO_CONCURRENCY=4
[root@localhost dm_celery_core_repro_pool_fixed]# sh run_crash_mode.sh
enqueued index=0 task_id=add31502-3c13-46b0-8d64-fd97b0ced2aa queue=dm_repro_crash
enqueued index=1 task_id=58ec37bf-b660-44e4-b77d-3774cc7e9b7b queue=dm_repro_crash
enqueued index=2 task_id=0d359053-5338-4944-b792-fb58f3f85ff5 queue=dm_repro_crash
enqueued index=3 task_id=4afa2cde-98d2-45b0-91fe-7b05c66852b4 queue=dm_repro_crash
/Work/billiard/venv_demo/lib/python3.10/site-packages/celery/platforms.py:829: SecurityWarning: You're running the worker with superuser privileges: this is
absolutely not recommended!
Please specify a different user using the --uid option.
User information: uid=0 euid=0 gid=0 egid=0
warnings.warn(SecurityWarning(ROOT_DISCOURAGED.format(
-------------- celery@prefork_dm_repro_crash_4 v5.3.6 (emerald-rush)
--- ***** -----
-- ******* ---- Linux-4.19.90-89.11.v2401.ky10.x86_64-x86_64-with-glibc2.28 2026-09-18 11:00:56
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app: dm_celery_repro:0x7f0cd0c888e0
- ** ---------- .> transport: redis://:**@127.0.0.1:6379/0
- ** ---------- .> results: disabled://
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> dm_repro_crash exchange=dm_repro_crash(direct) key=dm_repro_crash
[tasks]
. probe.tasks.polling_offline_log_import_check
[2026-09-18 11:00:56,263: INFO/MainProcess] worker_before_create_process: parent opened Django DB connection row=(1,)
[2026-09-18 11:00:56,314: INFO/MainProcess] worker_before_create_process: parent opened Django DB connection row=(1,)
[2026-09-18 11:00:56,326: INFO/ForkPoolWorker-1] worker_process_init: Django DB connections closed
[2026-09-18 11:00:56,327: INFO/ForkPoolWorker-1] worker_process_init: inherited pool discarded (kept 1 ref(s)), will rebuild per-worker
[2026-09-18 11:00:56,393: INFO/MainProcess] worker_before_create_process: parent opened Django DB connection row=(1,)
[2026-09-18 11:00:56,403: INFO/ForkPoolWorker-2] worker_process_init: Django DB connections closed
[2026-09-18 11:00:56,404: INFO/ForkPoolWorker-2] worker_process_init: inherited pool discarded (kept 1 ref(s)), will rebuild per-worker
[2026-09-18 11:00:56,467: INFO/MainProcess] worker_before_create_process: parent opened Django DB connection row=(1,)
[2026-09-18 11:00:56,481: INFO/ForkPoolWorker-3] worker_process_init: Django DB connections closed
[2026-09-18 11:00:56,481: INFO/ForkPoolWorker-3] worker_process_init: inherited pool discarded (kept 1 ref(s)), will rebuild per-worker
[2026-09-18 11:00:56,616: WARNING/MainProcess] /Work/billiard/venv_demo/lib/python3.10/site-packages/celery/worker/consumer/consumer.py:507: CPendingDeprecationWarning: The broker_connection_retry configuration setting will no longer determine
whether broker connection retries are made during startup in Celery 6.0 and above.
If you wish to retain the existing behavior for retrying connections on startup,
you should set broker_connection_retry_on_startup to True.
warnings.warn(
[2026-09-18 11:00:56,624: INFO/ForkPoolWorker-4] worker_process_init: Django DB connections closed
[2026-09-18 11:00:56,625: INFO/ForkPoolWorker-4] worker_process_init: inherited pool discarded (kept 1 ref(s)), will rebuild per-worker
[2026-09-18 11:00:56,638: INFO/MainProcess] Connected to redis://:**@127.0.0.1:6379/0
[2026-09-18 11:00:56,640: WARNING/MainProcess] /Work/billiard/venv_demo/lib/python3.10/site-packages/celery/worker/consumer/consumer.py:507: CPendingDeprecationWarning: The broker_connection_retry configuration setting will no longer determine
whether broker connection retries are made during startup in Celery 6.0 and above.
If you wish to retain the existing behavior for retrying connections on startup,
you should set broker_connection_retry_on_startup to True.
warnings.warn(
[2026-09-18 11:00:56,644: INFO/MainProcess] mingle: searching for neighbors
[2026-09-18 11:00:57,677: INFO/MainProcess] mingle: all alone
[2026-09-18 11:00:57,716: INFO/MainProcess] celery@prefork_dm_repro_crash_4 ready.
[2026-09-18 11:01:01,061: INFO/MainProcess] Task probe.tasks.polling_offline_log_import_check[add31502-3c13-46b0-8d64-fd97b0ced2aa] received
[2026-09-18 11:01:01,064: INFO/ForkPoolWorker-1] [Celery_Task] scheduled DB probe task id=add31502-3c13-46b0-8d64-fd97b0ced2aa repeat=1
[2026-09-18 11:01:01,065: INFO/ForkPoolWorker-1] [Celery_Task] closing all Django DB connections before query task id=add31502-3c13-46b0-8d64-fd97b0ced2aa
[2026-09-18 11:01:01,083: INFO/ForkPoolWorker-1] [Celery_Task] DB probe ok task id=add31502-3c13-46b0-8d64-fd97b0ced2aa iteration=0 row=(1,)
[2026-09-18 11:01:01,084: INFO/ForkPoolWorker-1] Task probe.tasks.polling_offline_log_import_check[add31502-3c13-46b0-8d64-fd97b0ced2aa] succeeded in 0.020270170003641397s: {'task_id': 'add31502-3c13-46b0-8d64-fd97b0ced2aa', 'repeat': 1}
[2026-09-18 11:01:01,084: INFO/ForkPoolWorker-1] task_postrun: probe.tasks.polling_offline_log_import_check [add31502-3c13-46b0-8d64-fd97b0ced2aa] executed in 0.0200s
[2026-09-18 11:01:01,130: INFO/MainProcess] Task probe.tasks.polling_offline_log_import_check[58ec37bf-b660-44e4-b77d-3774cc7e9b7b] received
[2026-09-18 11:01:01,132: INFO/ForkPoolWorker-1] [Celery_Task] scheduled DB probe task id=58ec37bf-b660-44e4-b77d-3774cc7e9b7b repeat=1
[2026-09-18 11:01:01,132: INFO/ForkPoolWorker-1] [Celery_Task] closing all Django DB connections before query task id=58ec37bf-b660-44e4-b77d-3774cc7e9b7b
[2026-09-18 11:01:01,134: INFO/ForkPoolWorker-1] [Celery_Task] DB probe ok task id=58ec37bf-b660-44e4-b77d-3774cc7e9b7b iteration=0 row=(1,)
[2026-09-18 11:01:01,134: INFO/ForkPoolWorker-1] Task probe.tasks.polling_offline_log_import_check[58ec37bf-b660-44e4-b77d-3774cc7e9b7b] succeeded in 0.002888534014346078s: {'task_id': '58ec37bf-b660-44e4-b77d-3774cc7e9b7b', 'repeat': 1}
[2026-09-18 11:01:01,134: INFO/ForkPoolWorker-1] task_postrun: probe.tasks.polling_offline_log_import_check [58ec37bf-b660-44e4-b77d-3774cc7e9b7b] executed in 0.0029s
[2026-09-18 11:01:01,191: INFO/MainProcess] Task probe.tasks.polling_offline_log_import_check[0d359053-5338-4944-b792-fb58f3f85ff5] received
[2026-09-18 11:01:01,193: INFO/ForkPoolWorker-1] [Celery_Task] scheduled DB probe task id=0d359053-5338-4944-b792-fb58f3f85ff5 repeat=1
[2026-09-18 11:01:01,193: INFO/ForkPoolWorker-1] [Celery_Task] closing all Django DB connections before query task id=0d359053-5338-4944-b792-fb58f3f85ff5
[2026-09-18 11:01:01,195: INFO/ForkPoolWorker-1] [Celery_Task] DB probe ok task id=0d359053-5338-4944-b792-fb58f3f85ff5 iteration=0 row=(1,)
[2026-09-18 11:01:01,195: INFO/ForkPoolWorker-1] Task probe.tasks.polling_offline_log_import_check[0d359053-5338-4944-b792-fb58f3f85ff5] succeeded in 0.0026033629837911576s: {'task_id': '0d359053-5338-4944-b792-fb58f3f85ff5', 'repeat': 1}
[2026-09-18 11:01:01,195: INFO/ForkPoolWorker-1] task_postrun: probe.tasks.polling_offline_log_import_check [0d359053-5338-4944-b792-fb58f3f85ff5] executed in 0.0025s
[2026-09-18 11:01:01,244: INFO/MainProcess] Task probe.tasks.polling_offline_log_import_check[4afa2cde-98d2-45b0-91fe-7b05c66852b4] received
[2026-09-18 11:01:01,246: INFO/ForkPoolWorker-1] [Celery_Task] scheduled DB probe task id=4afa2cde-98d2-45b0-91fe-7b05c66852b4 repeat=1
[2026-09-18 11:01:01,247: INFO/ForkPoolWorker-1] [Celery_Task] closing all Django DB connections before query task id=4afa2cde-98d2-45b0-91fe-7b05c66852b4
[2026-09-18 11:01:01,249: INFO/ForkPoolWorker-1] [Celery_Task] DB probe ok task id=4afa2cde-98d2-45b0-91fe-7b05c66852b4 iteration=0 row=(1,)
[2026-09-18 11:01:01,249: INFO/ForkPoolWorker-1] Task probe.tasks.polling_offline_log_import_check[4afa2cde-98d2-45b0-91fe-7b05c66852b4] succeeded in 0.003110619989456609s: {'task_id': '4afa2cde-98d2-45b0-91fe-7b05c66852b4', 'repeat': 1}
[2026-09-18 11:01:01,249: INFO/ForkPoolWorker-1] task_postrun: probe.tasks.polling_offline_log_import_check [4afa2cde-98d2-45b0-91fe-7b05c66852b4] executed in 0.0032s
worker: Warm shutdown (MainProcess)
[root@localhost dm_celery_core_repro_pool_fixed]#
文章
阅读量
获赞
