| 380 | |
| 381 | Transaction management outside the web server |
| 382 | ============================================= |
| 383 | |
| 384 | Django's typical transaction and connection management does not work outside |
| 385 | the web server context. |
| 386 | |
| 387 | You may, for example, setup Django to run some recurring tasks in their own |
| 388 | threads and notice that they leave behind unfinished database connection |
| 389 | processes (in PostgreSQL this manifests as the connections being listed as |
| 390 | "Idle In Transaction"). |
| 391 | |
| 392 | Looking through the Postgres logs, you may find that the transactions aren't |
| 393 | being completed (no ``ROLLBACK``). Switching to manual transaction management |
| 394 | and doing the rollback manually will work, but this still leaves the processes |
| 395 | as "Idle". Finally, calling ``django.db.connection.close()`` will end these |
| 396 | "Idle" processes. What's going on here and how can we fix it? |
| 397 | |
| 398 | Transactions |
| 399 | ------------ |
| 400 | |
| 401 | Django's default :ref:`autocommit behavior<topics-db-transactions-autocommit>` |
| 402 | holds true for threaded functions. However, as noted above: |
| 403 | |
| 404 | As soon as you perform an action that needs to write to the database, |
| 405 | Django produces the ``INSERT/UPDATE/DELETE`` statements and then does the |
| 406 | ``COMMIT``. There’s no implicit ``ROLLBACK``. |
| 407 | |
| 408 | That last sentence is very literal. Django DOES NOT issue a ``ROLLBACK`` |
| 409 | command unless something in Django has set the dirty flag. If your functions |
| 410 | are only performing ``SELECT`` statements, it won't set the dirty flag to |
| 411 | trigger a ``COMMIT``. |
| 412 | |
| 413 | This goes against the fact that PostgreSQL thinks the transaction requires a |
| 414 | ``ROLLBACK`` because Django issued a ``SET`` command for the timezone. |
| 415 | |
| 416 | Connections |
| 417 | ----------- |
| 418 | |
| 419 | Connection management is where things get tricky. Django uses |
| 420 | :data:`~django.core.signals.request_finished` to close the database connection |
| 421 | it normally uses. Since nothing normally happens in Django that doesn't involve |
| 422 | a request, you take this behavior for granted. |
| 423 | |
| 424 | In this case, though, there isn't a request because the thread is outside the |
| 425 | web server process. No request means no signal. No signal means the database |
| 426 | connection won't be closed. |
| 427 | |
| 428 | Going back to transactions, it turns out that simply issuing a call to |
| 429 | ``django.db.connection.close()`` in the absence of any changes to the |
| 430 | transaction management also issues the meeded ``ROLLBACK`` statement. |
| 431 | |
| 432 | Solution |
| 433 | -------- |
| 434 | |
| 435 | The solution is to allow Django's transaction management to proceed as usual |
| 436 | and to simply close the connection one of three ways: |
| 437 | |
| 438 | 1. Write a decorator that closes the connection and wrap the necessary |
| 439 | functions in it. |
| 440 | 2. Hook into the existing :mod:`request signals<django.core.signals>` to |
| 441 | have Django close the connection. |
| 442 | 3. Close the connection manually (``django.db.connection.close()``) at the end |
| 443 | of the function. |