281
entity. This means that each thread has its own undo log that is not synchronized with
undo logs of other threads.
Figure 14-1 illustrates the internals of what happens within the transaction when
we call the increment() function in Listing 14-2. For illustrative purposes, we only
describe two threads. Each thread executes concurrent transactions to increment the
value of counter allocated in persistent memory. We assume the initial value of counter
is 0 and the first thread acquires the lock, while the second thread waits on the lock.
Inside the critical section, the first thread adds the initial value of counter to the undo
log and increments it. The mutex is released when execution flow leaves the lambda
scope, but the transaction has not committed the update to persistent memory. The
changes become immediately visible to the second thread. After a user-provided lambda
is executed, the transaction needs to flush all changes to persistent memory to mark
the change(s) as committed. Concurrently, the second thread adds the current value of
counter, which is now 1, to its undo log and performs the increment operation. At that
moment, there are two uncommitted transactions. The undo log of Thread 1 contains
counter = 0, and the undo log of Thread 2 contains counter = 1. If Thread 2 commits
its transaction while Thread 1 aborts its transaction for some reason (crash or abort), the
incorrect value of counter will be restored from the undo log of Thread 1.
The solution is to hold the mutex until the transaction is fully committed, and the data
has been successfully flushed to persistent memory. Otherwise, changes made by one
transaction become visible to concurrent transactions before it is persisted and committed.
Listing 14-3 demonstrates how to implement the increment() function correctly.
Figure 14-1. Illustrative execution of the Listing 14-2 example
Chapter 14 ConCurrenCy and persistent MeMory
entity. This means that each thread has its own undo log that is not synchronized with
undo logs of other threads.
Figure 14-1 illustrates the internals of what happens within the transaction when
we call the increment() function in Listing 14-2. For illustrative purposes, we only
describe two threads. Each thread executes concurrent transactions to increment the
value of counter allocated in persistent memory. We assume the initial value of counter
is 0 and the first thread acquires the lock, while the second thread waits on the lock.
Inside the critical section, the first thread adds the initial value of counter to the undo
log and increments it. The mutex is released when execution flow leaves the lambda
scope, but the transaction has not committed the update to persistent memory. The
changes become immediately visible to the second thread. After a user-provided lambda
is executed, the transaction needs to flush all changes to persistent memory to mark
the change(s) as committed. Concurrently, the second thread adds the current value of
counter, which is now 1, to its undo log and performs the increment operation. At that
moment, there are two uncommitted transactions. The undo log of Thread 1 contains
counter = 0, and the undo log of Thread 2 contains counter = 1. If Thread 2 commits
its transaction while Thread 1 aborts its transaction for some reason (crash or abort), the
incorrect value of counter will be restored from the undo log of Thread 1.
The solution is to hold the mutex until the transaction is fully committed, and the data
has been successfully flushed to persistent memory. Otherwise, changes made by one
transaction become visible to concurrent transactions before it is persisted and committed.
Listing 14-3 demonstrates how to implement the increment() function correctly.
Figure 14-1. Illustrative execution of the Listing 14-2 example
Chapter 14 ConCurrenCy and persistent MeMory
