Opened 7 hours ago
Last modified 7 hours ago
#36957 new Bug
Django psycopg connection pool + fork() — at Version 1
| Reported by: | Anna | Owned by: | |
|---|---|---|---|
| Component: | Uncategorized | Version: | 6.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Django's PostgreSQL backend stores psycopg_pool.ConnectionPool objects in a class-level dict (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 cause
# django/db/backends/postgresql/base.py
class DatabaseWrapper(BaseDatabaseWrapper):
_connection_pools = {} # class-level dict — survives fork()
@property
def pool(self):
if self.alias not in self._connection_pools:
pool = ConnectionPool(...)
self._connection_pools.setdefault(self.alias, pool)
return self._connection_pools[self.alias]
Workaround
# gunicorn.conf.py
def post_fork(server, worker):
from django.db.backends.postgresql.base import DatabaseWrapper
DatabaseWrapper._connection_pools.clear()
Suggested fix
Use os.register_at_fork(after_in_child=...) to clear _connection_pools in child processes, or check os.getpid() in the pool property and recreate when it differs from the creating process.
Tested with
Django 6.0.2
psycopg 3.2.x – 3.3.2
psycopg-pool 3.2.x – 3.3.0
gunicorn 25.x (--worker-class asgi)
Python 3.12 – 3.14
The minimal reproducible example project is in the attachments
Change History (2)
by , 7 hours ago
| Attachment: | django-pool-fork-bug.zip added |
|---|
comment:1 by , 7 hours ago
| Description: | modified (diff) |
|---|