#30441: Connection leak if CONN_MAX_AGE == None
-----------------------------------------+------------------------
               Reporter:  cryptogun      |          Owner:  nobody
                   Type:  Bug            |         Status:  new
              Component:  Uncategorized  |        Version:  2.2
               Severity:  Normal         |       Keywords:
           Triage Stage:  Unreviewed     |      Has patch:  0
    Needs documentation:  0              |    Needs tests:  0
Patch needs improvement:  0              |  Easy pickings:  0
                  UI/UX:  0              |
-----------------------------------------+------------------------
 PostgreSQL default max connection is 100.
 if `CONN_MAX_AGE == None` in setting.py:
 Every time I authenticate() a user, the connection is opened but not
 closed.
 {{{user = authenticate(request=request, username='admin',
 password='123456')}}}

 if `CONN_MAX_AGE == 0`:
 The connection will be closed properly.

 {{{
 Traceback (most recent call last):
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\utils\autoreload.py", line 225, in wrapper
     fn(*args, **kwargs)
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\core\management\commands\runserver.py", line 120, in
 inner_run
     self.check_migrations()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\core\management\base.py", line 442, in check_migrations
     executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\migrations\executor.py", line 18, in __init__
     self.loader = MigrationLoader(self.connection)
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\migrations\loader.py", line 49, in __init__
     self.build_graph()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\migrations\loader.py", line 212, in build_graph
     self.applied_migrations = recorder.applied_migrations()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\migrations\recorder.py", line 61, in applied_migrations
     if self.has_table():
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\migrations\recorder.py", line 44, in has_table
     return self.Migration._meta.db_table in
 self.connection.introspection.table_names(self.connection.cursor())
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\base\base.py", line 256, in cursor
     return self._cursor()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\base\base.py", line 233, in _cursor
     self.ensure_connection()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\base\base.py", line 217, in ensure_connection
     self.connect()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\utils.py", line 89, in __exit__
     raise dj_exc_value.with_traceback(traceback) from exc_value
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\base\base.py", line 217, in ensure_connection
     self.connect()
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\base\base.py", line 194, in connect
     self.connection = self.get_new_connection(conn_params)
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\django\db\backends\postgresql\base.py", line 178, in
 get_new_connection
     connection = Database.connect(**conn_params)
   File "C:\Users\me\AppData\Local\Programs\Python\Python36\lib\site-
 packages\psycopg2\__init__.py", line 126, in connect
     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
 django.db.utils.OperationalError: FATAL:  remaining connection slots are
 reserved for non-replication superuser connections
 }}}

 Bug reproduce (with attached project):
 1. set CONN_MAX_AGE to None:
 {{{
 # settings.py
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
         'CONN_MAX_AGE': None,
     }
 }
 }}}
 2. Print and trace db connection:
 Modify `site-packages\django\db\backends\base\base.py`
 {{{
 # site-packages\django\db\backends\base\base.py
 # Add a print line:
     def ensure_connection(self):
         """Guarantee that a connection to the database is established."""
         if self.connection is None:
             with self.wrap_database_errors:
                 print('open connection +++++++++++++++++++')
                 self.connect()
 }}}
 {{{
 # Add a print line:
     def close(self):
         """Close the connection to the database."""
         self.validate_thread_sharing()
         self.run_on_commit = []

         # Don't call validate_no_atomic_block() to avoid making it
 difficult
         # to get rid of a connection in an invalid state. The next
 connect()
         # will reset the transaction state anyway.
         if self.closed_in_transaction or self.connection is None:
             return
         try:
             print('close connection ......................')
             self._close()
         finally:
             if self.in_atomic_block:
                 self.closed_in_transaction = True
                 self.needs_rollback = True
             else:
                 self.connection = None
 }}}
 3. Open a new incognito browser <kbd>Ctrl</kbd><kbd>Shift</kbd> +
 <kbd>n</kbd>.
 4. Goto landing page.
 5. Check Django log output.
 `open connection +++++++++++++++++++`
 No close log printed.
 6. Goto 3. and try as many times as you want.
 7. Switch to PostgreSQL backend, refresh 100 times, and you will get the
 above `OperationalError`.

 8. Set CONN_MAX_AGE to `0` in settings.py.
 9. Retry 3~5
 Now the close was executed.
 {{{
 open connection +++++++++++++++++++
 [04/May/2019 16:09:53] "GET / HTTP/1.1" 200 2
 close connection ......................
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/30441>
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 django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/052.805d5774cc21cf858939d0aa49f9c273%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to