medium +20 pts

Reentrant Lock Manager

Build a thread-safe lock that the same thread can acquire multiple times

Implement a class `ReentrantLock` that provides a reentrant, thread-safe lock. Unlike a plain mutex, the same thread can call `acquire()` multiple times without deadlocking. Each call to `acquire()` increments an internal counter, and each call to `release()` decrements it. The lock is only fully released when the counter returns to zero, at which point other threads can acquire it. Define the class with the following methods: - `__init__(self)`: Initializes the lock. The lock starts unlocked. - `acquire(self) -> None`: Acquires the lock. If the lock is already held by the calling thread, it returns immediately and increments the hold count. If the lock is held by a different thread, it blocks until the lock is available (counter becomes zero). If the lock is unlocked, it takes ownership and sets the hold count to 1. - `release(self) -> None`: Releases one hold on the lock. Decrements the internal counter. If the counter reaches zero, the lock becomes unlocked and other threads can acquire it. If called when the lock is not held by the calling thread or when the counter is zero, raise a `RuntimeError`. - `locked` property: Returns `True` if the lock is currently held (counter > 0), `False` otherwise. - `owner` property: Returns the integer thread identifier of the thread that currently holds the lock, or `None` if the lock is unlocked. In addition to the class, provide the following standalone helper functions that are used by the automated tests: - `make_lock() -> ReentrantLock`: Returns a newly created `ReentrantLock` instance. - `acquire_twice_same_thread() -> str`: In the main thread, create a lock, call `acquire()` twice, then check `locked` is `True` and `owner` equals the current thread identifier. Then call `release()` twice. Return `'two_holds'` after the second `release()`. - `acquire_release_single() -> str`: In the main thread, create a lock, call `acquire()`, verify `locked` is `True`, then `release()`, verify `locked` is `False`. Return `'released'` after the successful `release()`. - `multi_thread_release_and_acquire() -> str`: Spawn two threads, each of which acquires the lock once and then releases it once. The threads must run sequentially (meaning the second thread should not acquire until the first has released). The function should wait for both threads to finish and return `'success'` if no deadlock or exception occurred. - `release_not_owner_raises() -> str`: In the main thread, create a lock, acquire it, then attempt to call `release()` from a different thread. Catch the `RuntimeError` and return `'runtime_error'`. Your implementation must be thread-safe. Use Python's `threading` module primitives (e.g., `threading.Lock`, `threading.Condition`, `threading.get_ident`) to manage synchronization. Do not use `threading.RLock` directly.

Constraints

- This is a concurrency problem. Your solution will be tested with multiple threads. - You may use only the Python standard library, especially the `threading` module. - The number of nested acquisitions is not bounded except by available memory. - The `release` method must raise `RuntimeError` if the lock is not held by the calling thread or if the counter is zero. - Ensure your implementation does not deadlock and is fair enough for reasonable test scenarios.

Example

```python
import threading

lock = ReentrantLock()

def worker():
    lock.acquire()
    lock.acquire()
    print(lock.locked)  # True
    print(lock.owner)   # the calling thread's ident
    lock.release()
    print(lock.locked)  # True
    lock.release()
    print(lock.locked)  # False

threads = [threading.Thread(target=worker) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()
```
20 points ~20 min

Recent Submissions

No submissions yet — hit Run Tests to try!

Hints

Use a `threading.Condition` as the underlying synchronization primitive to protect the state and allow blocking.
Keep track of the owner thread ID and a counter. In `acquire`, if the current thread is the owner, just increment the counter; otherwise, wait until the counter is zero.
In `release`, check that the current thread is the owner and the counter > 0. Decrement; if counter becomes zero, clear the owner and notify waiting threads.
Ensure the helper functions use the class you defined and return the expected string values.
Python 3
All tests passed!
Test Results
Press Ctrl+Enter or click Run Tests to execute your code.