'DispatchQueue Timeout mechanism not firing / working

I'm running into a bit of a conundrum with the following code snippet. The purpose of this code is to read bytes from an NSInputStream and process them accordingly. However, we have noticed that sometimes, the inputStream.read() function can block indefinitely.

In order to address this, we implemented a timeout mechanism, shown below. We know the read is occurring because we get the log line that says "Reading Stream", which occurs after the dispatch_sync() block, but it will still continue to indefinitely hang at times and the timeout block, which was scheduled on the main thread, is never firing. Why is this not working as intended?

When the inputStream.read() function doesn't block indefinitely, the following code snippet works perfectly, and the timeout block is cancelled before it fires. Also, other independent dispatch_after() blocks get called elsewhere in our code, and the syntax is exactly the same as those areas that do work, so I'm wondering why this timeout pattern isn't working.

- (void)readStreamData:(nonnull NSInputStream*)stream {
    NSUInteger fileDataSize = self.fileDataSize;
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(self.streamQueue, ^{
        @autoreleasepool {
            if(weakSelf == nil) return;
            NSMutableData* buffer = [[NSMutableData alloc] initWithLength:fileDataSize];
            
            // Timeout Read if it does not return within 5 seconds
            __block BOOL timeout = YES;
            dispatch_sync(dispatch_get_main_queue(), ^{
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    if(timeout) {
                        [weakSelf.delegate objectTransferService:weakSelf error:[NSError errorWithDomain:PBObjectTransferServiceErrorDomain
                                                                                                    code:-weakSelf.transferState
                                                                                                userInfo:@{NSLocalizedDescriptionKey : @"Reading Stream Timed Out"}]];
                    }
                    timeout = NO;
                });
            });
            
            if(!weakSelf.readStreamLog) {
                PBLogWeakDebug(@"Reading Stream");
                weakSelf.readStreamLog = YES;
            }
            NSInteger read = [stream read:buffer.mutableBytes maxLength:buffer.length];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                if (timeout) {
                    PBLogWeakVerbose(@"Cancelling Read Stream Timeout");
                    timeout = NO;
                    if(weakSelf == nil) return;

                    // Do some additional Non-blocking WORK
                    // Non-blocking WORK
                    // Non-blocking WORK
            });
        }
    });
}


Solution 1:[1]

To a certain degree, you're "crossing the streams" here (to use an American pop-culture reference). NSInputStream is a run-loop-based async I/O idiom, and GCD is not a run-loop.

At the risk of being less than helpful, you should probably either not use NSInputStream or not use Grand Central Dispatch. If you want to use NSInputStream more than GCD, then manage your concurrency with a run-loop and provide an NSStreamDelegate to handle incoming data, and if you want to use GCD more than NSStream, use GCD's IO functions. They're pretty good.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ipmcc