Jim, Kevin, everybody,
I am working on a group of bugs related to transactions not returning
the correct data every time. These issues are most likely related to our
implementation of isolation levels and visibility of record versions. I
have found the cause for one of these bugs but would like input on what
is the best way to solve it.
The bug I have been analyzing is Bug #41357 "falcon.falcon_bug_34351_C
fails with assertion IS_CONSISTENT_READ". The assert happens in
Table::fetchForUpdate() due to the call to
Transaction::getRelativeState() returns CommittedInvisible for a
recordVersion when the current
transaction is running in isolation level TRANSACTION_WRITE_COMMITTED.
This return value
should only happen if the isolation level is TRANSACTION_CONSISTENT_READ.
After having reproduced this assert several times with instrumentation
in the source it
seems like this problems is caused by that the Transaction object that
object has a pointer to and which is used for computing the relative
state between the two
transaction "changes state" during the execution of
Here is what normally happens when the CommittedInvisible state is
reached by the following code path:
1. Transaction::getRelativeState() is called and the following code is run:
if (transaction->state == Committed)
// Return CommittedVisible if the other trans has a lower TransId and
// it was committed when we started.
if (visible (transaction, transId, FOR_WRITING))
when this code is run the transaction->state == Committed (and the if
test is true).
This results in Transaction::visible() being called.
2. In the code for Transaction::visible() the following code is run:
// If the other transaction is not yet committed, the trans is not visible.
if (transaction->state != Committed)
When the same test now is evaluated "transaction->state != Committed"
this test too
succeed and returns that the transaction should not be visible.
So based on traces in the code and studying core files it seems like the
transaction->state variable changes value from being Committed to not
during the execution of these two methods. This leads to
Transaction::visible() returning "false" which again leads to
Transaction::getRelativeState() returning CommittedInvisible (and the
The reason for the "mysterious" state change (at least in some of the
crashes I have reproduced) is that the transaction object which the
record version points too is deleted by another thread and thus the
check for committed (or not) in Transaction::visible() is now checking
This situation seems very similar to an issue Kevin had (and still
have?) with the scavenger checking which records that can be scavenged.
-how do we either handle that Transaction object that is being pointed
to by RecordVersion objects are deleted at "any time". Like the code for
Transaction::getRelativeState and Transaction::visible() is now this can
lead to wrong conclusions about which records are visible or not.
-or do we need to introduce some kind of locking so that we can safely
use RecordVersion objects and the Transaction object they normally
-or can we do something about the order we clean up old transaction
objects versus the record version objects to avoid that we have record
object without a transaction object?
I do not know enough about this part of the code (yet) to propose a good
solution so any suggestions would be appreciated.