HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Synchronization of transaction processing

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
processingsynchronizationtransaction

Problem

There's a server which does the following:

  • Receive request with transaction id



  • Load corresponding transaction from storage. New transaction object is returned each time



  • Process transaction



  • Save updated transaction back to storage



The task is to properly synchronize steps 2, 3 and 4.

Since for each request new instance of transaction object will be created, I decided to create TransactionService class with AcquireTransactionLock method to be called by request handling code.

`class Transaction
{
public int Id { get; set; }
public int Counter { get; set; }

public void Process()
{
Counter++;
}
}

class TransactionService
{
private readonly object syncRoot;
private readonly Dictionary> transactionLockMap; // id -> (referenceCount, lock).

public TransactionService()
{
syncRoot = new object();
transactionLockMap = new Dictionary>();
}

public IDisposable AcquireTransactionLock(int transactionId)
{
return new TransactionLock(transactionId, this);
}

class TransactionLock : IDisposable
{
private readonly int transactionId;
private readonly TransactionService transactionService;

public TransactionLock(int transactionId, TransactionService transactionService)
{
this.transactionId = transactionId;
this.transactionService = transactionService;

Tuple transactionLock;

lock (transactionService.syncRoot)
{
if (!transactionService.transactionLockMap.TryGetValue(transactionId, out transactionLock))
{
transactionLock = Tuple.Create(1, new object());
}
else
{
transactionLock = Tuple.Create(transactionLock.Item1 + 1, transactionLock.Item2);
}

transactionService.transactionLockMap[transactionId] = transactionLock;
}

Solution

I cannot verify this pattern for its correctnes but I find you can simplify and make the TransactionLocker better testable by removing the trasaction logic from it.

Let the Locker do only the locking and delegate the actions by using two lambdas: one for onLocked and the other one for onUnlocking.

class Locker : IDisposable
{   
    private readonly object _syncRoot;
    private readonly Func _onLocked;
    private readonly Action _onUnlocking;
    private object _lockedObject;

    public Locker(object syncRoot, Func onLocked, Action onUnlocking)
    {
        _syncRoot = syncRoot;
        _onLocked = onLocked;
        _onUnlocking = onUnlocking;     

        lock (_syncRoot)
        {
            _lockedObject = _onLocked();
        }

        Monitor.Enter(_lockedObject);
    }

    public void Dispose()
    {
        lock (_syncRoot)
        {
            _onUnlocking();
        }

        Monitor.Exit(_lockedObject);
    }
}


Now you can even reuse the Locker somewhere else because it's not longer tightly coupled to the transation.

Code Snippets

class Locker : IDisposable
{   
    private readonly object _syncRoot;
    private readonly Func<object> _onLocked;
    private readonly Action _onUnlocking;
    private object _lockedObject;

    public Locker(object syncRoot, Func<object> onLocked, Action onUnlocking)
    {
        _syncRoot = syncRoot;
        _onLocked = onLocked;
        _onUnlocking = onUnlocking;     

        lock (_syncRoot)
        {
            _lockedObject = _onLocked();
        }

        Monitor.Enter(_lockedObject);
    }

    public void Dispose()
    {
        lock (_syncRoot)
        {
            _onUnlocking();
        }

        Monitor.Exit(_lockedObject);
    }
}

Context

StackExchange Code Review Q#150775, answer score: 3

Revisions (0)

No revisions yet.