Imagine an operation that needs to be protected against race conditions, and therefore is run inside a Rails with_lock
block.
with_lock
starts a new transaction.
In this specific case, we’re not concerned about atomicity of the transaction, only about the protection against the block being run simultaneously (so we are only concerned about the lock).
The issue is that any rollback inside that block will rollback all operations performed in the block, which in this case is undesired. For instance:
class Foo < ApplicationRecord
def perform
with_lock do
expensive_calculation_and_persist_to_database
some_other_operation
end
end
private
def expensive_calculation_and_persist_to_database
ApplicationRecord.transaction(requires_new: true) do
update_columns(result: 42)
end
end
def some_other_operation
raise # this will issue a Rollback, which will also rollback the #expensive_calculation_and_persist_to_database transaction, even tough it used 'requires_new: true'
end
end
As you can see, we’d wish #expensive_calculation_and_persist_to_database
to be commited independently of the rollback caused by #some_other_operation
, so it wouldn’t need to be calculated again.
Note that wrapping #expensive_calculation_and_persist_to_database
in a transaction with requires_new: true
does not solve it; Rails docs clearly state that it uses savepoints to emulate nested transactions, which seems that they would be useful for rolling back just the nested transaction, but seem to not be useful when we want the opposite (keep the nested transaction commited, and rollback the parent one).
Is there a way to achieve this desired behavior?