#37406: BaseDatabaseSchemaEditor.__exit__ doesn't exit its atomic block when
deferred SQL raises
-------------------------------------+-------------------------------------
Reporter: Gavin Wahl(vendor) | Type:
| Uncategorized
Status: new | Component: Database
| layer (models, ORM)
Version: 6.1 | 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
-------------------------------------+-------------------------------------
`BaseDatabaseSchemaEditor` (normally accessed through
`connection.schema_editor`) implements the context manager protocol and
wraps `transaction.atomic()` in its `__enter__` and `__exit__`. In
`__exit__`, it runs the deferred SQL and then calls
`self.atomic.__exit__`. If the deferred SQL raises, the second call never
happens and the atomic block stays open.
After that the connection is stuck. `in_atomic_block` is True with no
atomic block on the stack, so nothing will ever commit. Once anything sets
`needs_rollback` you get `TransactionManagementError` on every query.
`close()` doesn't help because inside an atomic block it keeps the dead
connection object around and waits for an `Atomic.__exit__` to reset it.
This doesn't affect manage.py migrate from a shell, since the process
exits and the server rolls back. It matters if you catch the error and
keep going, e.g. tests, `call_command("migrate")`, or a tool using
MigrationExecutor directly.
There is an existing `test_migrations_not_applied_on_deferred_sql_failure`
but it has `atomic = False` so it doesn't hit this.
Minimal reproducer attached.
Related #29721
--
Ticket URL: <
https://code.djangoproject.com/ticket/37406>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.