Skip to content

Bug: FallbackUseLock potential deadlock - blocks on semaphore while holding NSLock #3571

@alltheseas

Description

@alltheseas

Problem

FallbackUseLock.incrementUserCount() in NdbUseLock.swift acquires ndbUserCountLock, then blocks on ndbAccessSemaphore.wait(). If another thread holds the semaphore and needs the lock, deadlock occurs.

Location

nostrdb/NdbUseLock.swift lines 148-156

private func incrementUserCount(maxTimeout: DispatchTimeInterval = .seconds(2)) throws {
    ndbUserCountLock.lock()
    defer { ndbUserCountLock.unlock() }
    
    // Signal that ndb cannot close while we have at least one user using ndb
    if ndbUserCount == 0 {
        try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)  // BLOCKS WHILE HOLDING LOCK
    }
    ndbUserCount += 1
}

Root Cause

Classic lock ordering violation: Thread A holds lock, waits for semaphore. Thread B holds semaphore, needs lock. Deadlock.

Impact

  • App freeze on iOS < 18 (FallbackUseLock is used)
  • Timeout after 2 seconds but thread is blocked during that time

Suggested Fix

Release lock before waiting on semaphore, or refactor to avoid nested blocking primitives.

Related Issues

Test Plan

  • Add concurrent access test with ThreadSanitizer
  • Test database close during active reads
  • Verify no deadlock under contention

Changelog-Fixed: Fix potential deadlock in FallbackUseLock

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working, or not working as intended

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions