The convoluted stack trace looks like this.
[07/Jan/2018 15:04:32] ERROR [rq.worker:757] django.db.transaction.TransactionManagementError: This is forbidden when an 'atomic' block is active.
Traceback (most recent call last):
File "/srv/gametraka_project_v3-master/apps/events/parse_gpx.py", line 174, in process_imu_data
records = create_imu_data(track, records)
File "/usr/lib/python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "/srv/gametraka_project_v3-master/apps/events/parse_gpx.py", line 72, in create_imu_data
Track.objects.get(pk=track.pk)
File "/srv/venv/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/srv/venv/lib/python3.6/site-packages/django/db/models/query.py", line 380, in get
self.model._meta.object_name
apps.events.models.model_track.DoesNotExist: Track matching query does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/srv/venv/lib/python3.6/site-packages/rq/worker.py", line 713, in perform_job
rv = job.perform()
File "/srv/venv/lib/python3.6/site-packages/rq/job.py", line 556, in perform
self._result = self._execute()
File "/srv/venv/lib/python3.6/site-packages/rq/job.py", line 562, in _execute
return self.func(*self.args, **self.kwargs)
File "/usr/lib/python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "/srv/gametraka_project_v3-master/apps/events/parse_gpx.py", line 177, in process_imu_data
transaction.rollback()
File "/srv/venv/lib/python3.6/site-packages/django/db/transaction.py", line 49, in rollback
get_connection(using).rollback()
File "/srv/venv/lib/python3.6/site-packages/django/db/backends/base/base.py", line 272, in rollback
self.validate_no_atomic_block()
File "/srv/venv/lib/python3.6/site-packages/django/db/backends/base/base.py", line 443, in validate_no_atomic_block
"This is forbidden when an 'atomic' block is active.")
django.db.transaction.TransactionManagementError: This is forbidden when an 'atomic' block is active.
From what can I tell, there are two parts to this story.
Something in the database was queried but the record does not exist.
apps.events.models.model_track.DoesNotExist: Track matching query does not exist.
The code that is responsible for is found at this file:
File "/srv/gametraka_project_v3-master/apps/events/parse_gpx.py", line 72, in create_imu_data
Track.objects.get(pk=track.pk)
At first I thought, the exception was not handled properly.
@transaction.atomic
def create_imu_data(track, imu_data):
# first guarantee that the track actually exists.
Track.objects.get(pk=track.pk)
...
return [] # assuming success, return a new empty container for more records
But looking at the function that calls create_imu_data, the exception is handled there. This leads me to the conclusion that the error is not in this function, all I had to do is write up some tests to have this assumption written down somewhere.
def test_create_imu_data_throws_exception_when_track_does_not_exist(self, mock_customer):
# build does not save in the database
mock_track = TrackFactory.build(id=2)
mock_imu_data = [
IMUDataFactory.build(track=mock_track),
IMUDataFactory.build(track=mock_track)
]
with self.assertRaises(Track.DoesNotExist):
result = create_imu_data(mock_track, mock_imu_data)
So, the investigation continues where the error occurs...
The error occurs outside create_imu_data, it clearly said "During handling of the above exception, another exception occurred:"
django.db.transaction.TransactionManagementError: This is forbidden when an 'atomic' block is active.
caused by:
File "/srv/gametraka_project_v3-master/apps/events/parse_gpx.py", line 177, in process_imu_data
transaction.rollback()
I am not really that familiar with atomic transactions on Django until I looked into this issue. Doing a little bit of reading, I found out that with an atomic transaction that's gone bad, Django will automatically rollback the database transaction once the exception handler is called.
That gave me a hint on how to fix the issue.
def some_function(self):
try:
records = create_imu_data(track, records)
except Track.DoesNotExist:
transaction.rollback()
...
return
We explicitly call transaction.rollback(), but the create_imu_data function is decorated with @transaction.atomic, hence we need not call transaction.rollback() here because it is done automatically for us.
The fix was simple enough, I just had to remove the transaction.rollback() calls in every exception handler.
I just had to write a test that says when you do an unnecessary rollback on a atomic transaction, you'll get the TransactionManagementError
@transaction.atomic
def some_function(self):
try:
# build does not save the track to db
mock_track = TrackFactory.build(id=2)
mock_imu_data = [
IMUDataFactory.build(track=mock_track),
IMUDataFactory.build(track=mock_track)
]
create_imu_data(mock_track, mock_imu_data)
except Track.DoesNotExist:
transaction.rollback()
def test_create_imu_data_caller_with_rollback(self):
with self.assertRaises(transaction.TransactionManagementError):
self.some_function()