Blocking call inside an @synchronized block

ID

objectivec.maintainability.blocking_call_in_critical_section

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Concurrency

Language

Objective-C

Description

Reports a call that waits - sleep, usleep, sleepForTimeInterval:, dispatch_sync, dispatch_barrier_sync, dispatch_semaphore_wait, dispatch_group_wait, pthread_cond_wait, pthread_join, waitUntilFinished and the dated lock and condition waits - made from inside an @synchronized body. The region ends at the closing brace that sits at the @synchronized keyword’s own indentation, so a wait placed after the block is not reported. Locks taken with NSLock rather than @synchronized are outside this rule.

Rationale

@synchronized holds a recursive mutex keyed on the object for as long as the body runs. Every other thread that synchronizes on that same object is parked for the whole duration of this wait, so a critical section that should have lasted microseconds now lasts as long as the slowest thing inside it, and throughput collapses under contention rather than degrading. Worse, the wait can be circular: dispatch_sync onto a queue whose block also synchronizes on this object, or a semaphore signalled only by a thread that needs the same lock, deadlocks outright. Neither failure is visible in a single-threaded test.

The following code illustrates the pattern detected by this rule:

@implementation Ledger
- (void)appendEntry:(NSString *)entry {
    @synchronized (self) {
        [_entries addObject:entry];
        // FLAGGED: Blocking call inside an @synchronized block
        sleep(1);
    }
}

- (void)flush { }
- (void)log:(NSString *)text { }
@end

Remediation

Take out of the critical section everything that does not need the lock. Copy or snapshot the shared state inside the block, close it, and do the waiting afterwards; where a result has to come back, use dispatch_async with a completion block instead of dispatch_sync. If the section only guards a counter or a collection, consider a serial dispatch queue or an atomic property, neither of which can be held across a wait.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved