#36957: Django psycopg connection pool + fork()
-------------------------------+--------------------------------------
     Reporter:  doc            |                    Owner:  (none)
         Type:  Bug            |                   Status:  new
    Component:  Uncategorized  |                  Version:  6.0
     Severity:  Normal         |               Resolution:
     Keywords:                 |             Triage Stage:  Unreviewed
    Has patch:  0              |      Needs documentation:  0
  Needs tests:  0              |  Patch needs improvement:  0
Easy pickings:  0              |                    UI/UX:  0
-------------------------------+--------------------------------------
Description changed by doc:

Old description:

> 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

New description:

 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

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36957#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019c9e8b022e-9e684d86-d7ab-438d-b61f-ae0acddd8b10-000000%40eu-central-1.amazonses.com.

Reply via email to