Problem
The ReaderWriterLockSlim in DuckDbInitializer.cs uses LockRecursionPolicy.NoRecursion. If an unhandled exception on the UI thread interrupts code that holds a read lock (preventing Dispose() / ExitReadLock() from running), the thread permanently owns a leaked read lock. Every subsequent EnterReadLock() on that thread throws LockRecursionException:
Recursive read lock acquisitions not allowed in this mode.
This manifests as the Overview tab failing to load server summaries every 30 seconds, permanently, until the app is restarted.
Root Cause
The ReaderWriterLockSlim is thread-affine and configured with NoRecursion. When an unhandled WPF crash (see #422) fires on the UI thread while a read lock is held, the lock is never released. The UI thread then permanently "owns" a phantom read lock, and any new EnterReadLock() on that thread throws.
Fix
Add resilience to AcquireReadLock():
- Use
TryEnterReadLock() with a timeout instead of EnterReadLock()
- Catch
LockRecursionException — if the thread already owns a read lock, proceed without acquiring another (the lock is already held, so the protection is already in place)
- This makes the lock self-healing: even if a previous crash leaked the lock, subsequent operations recover gracefully
Why not switch to SemaphoreSlim?
The ReaderWriterLockSlim has been working correctly since PR #218 (Feb 21). The locking design is sound — the only failure mode is leaked locks from unhandled exceptions. Adding resilience to the existing mechanism is lower risk than replacing the synchronization primitive.
Problem
The
ReaderWriterLockSliminDuckDbInitializer.csusesLockRecursionPolicy.NoRecursion. If an unhandled exception on the UI thread interrupts code that holds a read lock (preventingDispose()/ExitReadLock()from running), the thread permanently owns a leaked read lock. Every subsequentEnterReadLock()on that thread throwsLockRecursionException:This manifests as the Overview tab failing to load server summaries every 30 seconds, permanently, until the app is restarted.
Root Cause
The
ReaderWriterLockSlimis thread-affine and configured withNoRecursion. When an unhandled WPF crash (see #422) fires on the UI thread while a read lock is held, the lock is never released. The UI thread then permanently "owns" a phantom read lock, and any newEnterReadLock()on that thread throws.Fix
Add resilience to
AcquireReadLock():TryEnterReadLock()with a timeout instead ofEnterReadLock()LockRecursionException— if the thread already owns a read lock, proceed without acquiring another (the lock is already held, so the protection is already in place)Why not switch to SemaphoreSlim?
The
ReaderWriterLockSlimhas been working correctly since PR #218 (Feb 21). The locking design is sound — the only failure mode is leaked locks from unhandled exceptions. Adding resilience to the existing mechanism is lower risk than replacing the synchronization primitive.