pythonadvanced15 minutes

Fix the Deadlock Bug in Concurrent Transaction Simulation

Identify and fix a concurrency deadlock bug in a Python simulation of database transactions using locks. The broken code attempts to simulate two concurrent transactions that acquire multiple locks and cause a deadlock, resulting in a hang. Your task is to fix the code to avoid deadlocks while preserving the transactional behavior.

Challenge prompt

You are given a Python script simulating two concurrent transactions attempting to lock shared resources. The code causes a deadlock because each transaction waits indefinitely for locks held by the other transaction. Your challenge is to fix the deadlock by reordering locks or implementing a proper locking mechanism so that both transactions can complete successfully without deadlocks. You are not allowed to use external concurrency control libraries; instead, fix the logic within the existing threading and locking code.

Guidance

  • Analyze the order in which each transaction acquires locks and identify conflicting sequences that lead to deadlock.
  • Consider reordering lock acquisitions or implementing a consistent global lock acquisition order.
  • Test the fixed code by running the simulation multiple times to ensure no deadlocks occur.

Hints

  • Deadlocks often happen when multiple threads acquire multiple locks in different orders.
  • A common fix is imposing ordering on lock acquisition, so all threads attempt to acquire locks in the same global order.
  • Use try-finally blocks or context managers to ensure locks are always released properly.

Starter code

import threading

lock_a = threading.Lock()
lock_b = threading.Lock()


def transaction1():
    print('Transaction 1: Acquiring lock A')
    lock_a.acquire()
    print('Transaction 1: Acquired lock A')

    # Simulate work
    threading.Event().wait(1)

    print('Transaction 1: Acquiring lock B')
    lock_b.acquire()
    print('Transaction 1: Acquired lock B')

    print('Transaction 1: Releasing locks')
    lock_b.release()
    lock_a.release()


def transaction2():
    print('Transaction 2: Acquiring lock B')
    lock_b.acquire()
    print('Transaction 2: Acquired lock B')

    # Simulate work
    threading.Event().wait(1)

    print('Transaction 2: Acquiring lock A')
    lock_a.acquire()
    print('Transaction 2: Acquired lock A')

    print('Transaction 2: Releasing locks')
    lock_a.release()
    lock_b.release()


thread1 = threading.Thread(target=transaction1)
thread2 = threading.Thread(target=transaction2)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print('Both transactions completed')

Expected output

Transaction 1: Acquiring lock A Transaction 1: Acquired lock A Transaction 2: Acquiring lock B Transaction 2: Acquired lock B // Then either transaction proceeds without deadlock until both complete Both transactions completed

Core concepts

ConcurrencyThreadingLockingDeadlock Prevention

Challenge a Friend

Send this duel to someone else and see if they can solve it.