10

I'm seeing an issue wherein CFReadStreamRead, as part of a streamed file upload, never returns.

This seems to happen only on iOS7 — and far more often when debugging against a physical device than in the simulator — or at least, it's far more evident there.

We have an HTTP (or HTTPS, the problem occurs either way, with a locally-hosted or remote server) POST of a file, via straight-line, blocking (non-event-driven) CFNetwork calls. It's a necessity of the C code calling this handler; there's no provision for callbacks.

That's well and good, the network calls are happening in background threads and/or via async dispatch.

The network code in question boils down to (removing error handling for brevity):

CFReadStreamRef upload = CFReadStreamCreateWithFile(
  kCFAllocatorDefault, upload_file_url);
CFRelease(upload_file_url);
CFReadStreamOpen(upload);

CFReadStreamRef myReadStream = CFReadStreamCreateForStreamedHTTPRequest(
  kCFAllocatorDefault, myRequest, upload);

CFReadStreamOpen(myReadStream);

CFIndex numBytesRead = CFReadStreamRead(myReadStream, buf, sizeof(buf));

// etc.

On its own, this code wants to hang immediately under iOS7. If I add a loop with some calls to usleep before it (checking CFReadStreamHasBytesAvailable along the way), it will almost always succeed. Every few hundred tries, it will still fail, never returning. Again, the main thread is unaffected.

I'd hoped the GM would clear up this behavior, but it's still present.

Adding a runloop/callback method to watch for bytes-available events has no effect - when the call hangs, no events are seen, either.

Any suggestions as to why this is happening, or how it can be prevented? Anyone else seeing different CFReadStream behavior under iOS 7?

Paul Roub
  • 35,100
  • 27
  • 72
  • 83
  • I have similar issue: server hangs and client never get HTTP answer if client runs under iOS 7. Was fine with iOS 3-4-5-6. Client uses NSUrlConnection. Anyone: please share your observations. – rjobidon Sep 21 '13 at 02:23
  • There are valid and documented reasons why the function will block. So, first question: are you sure the file has not been read completely and it blocks somewhere in the middle? Secondly, are you sure the file has been opened successfully? – CouchDeveloper Sep 21 '13 at 23:14
  • Certain of both of those things, and fully expecting the function to block when it needs to. And yes, the (omitted) error handling checks that the file has been opened properly. – Paul Roub Sep 22 '13 at 00:46
  • You might add a bit more code. I'm assuming you are using `CFReadStreamCreateForStreamedHTTPRequest` in order to bind the `CFReadStream` to the request body. I haven't worked with CFNetwork much, but `NSMutableURLRequest` requires a `NSInputStream` which is NOT open (when set as HTTPBodyStream). So I'm wondering whether your CFReadStream must be open before setting it with `CFReadStreamCreateForStreamedHTTPRequest()`. – CouchDeveloper Sep 24 '13 at 23:38
  • The file read stream (upload) *is* opened before calling `CFReadStreamCreateForStreamedHTTPRequest`; `myReadStream` doesn't exist until afterwards. And note, this isn't "hey, how come my code doesn't work?" it's "how come my previously-happy (and very much based in Apple's samples) code now hangs unless I sleep for an indeterminate period first?" – Paul Roub Sep 24 '13 at 23:59
  • It would be helpful if you could provide the stack trace. It's important to know, whether the code hangs in an infinite loop, or waits in some semaphore for example. Also, there might be the case where the client, aka `CFReadStreamCreateForStreamedHTTPRequest`, tries to call `CFReadStreamRead` repeatedly but don't actually analyses a -1 result value (wild guessing). Note: conceptually, a file stream would never block during read. Internally, it calls system's `read` function - which does not block - well, does "not block for a longer duration" (there might be still kernel issues). – CouchDeveloper Sep 25 '13 at 08:42
  • See also `man 2 read`, how and when `read` might block. – CouchDeveloper Sep 25 '13 at 08:49
  • The calling thread is stuck in `mach_msg_trap`, and the only thread with socket activity consists solely of a thread-start followed by `select$DARWIN_EXTSN`. – Paul Roub Sep 25 '13 at 13:19
  • I have encounter exactly the same problem (it happens rare: once on 1000 read requests, but I have some continues stream so it is a big problem). I've added logs before `CFReadStreamRead`. When this problem appears `CFReadStreamHasBytesAvailable` returns `false` and `CFReadStreamGetStatus` returns `kCFStreamStatusOpen`. All other threads are doing nothing (run loops are waiting for new events). It is not server side problem since on iOS6 everything works perfectly (on android also), anyway after some time `CFReadStreamRead` should return with `-1` value on timeout, but it doesn't do that. – Marek R Oct 03 '13 at 15:00
  • I have another concept how to solve it but I don't have time to try it (it requires a bit more work). Assuming that this is threading issue in iOS library it is worth to try change code to asynchronous version, I mean try to register an event handler by [CFReadStreamScheduleWithRunLoop](https://developer.apple.com/library/mac/documentation/corefoundation/Reference/CFReadStreamRef/Reference/reference.html#//apple_ref/c/func/CFReadStreamScheduleWithRunLoop), and perform processing asynchronously. There is a small chance that this will prevent `CFReadStreamRead` from infinitive blocking. – Marek R Oct 11 '13 at 09:03
  • The issue is several layers deep in C code, as part of a module conforming to a vendor's plugin specification. It's synchronous or nothing. – Paul Roub Oct 11 '13 at 13:10

1 Answers1

0

I've try such nasty workaround and it works for me, problem is that I'm requesting delta values from server, so if something goes wrong I'm just fetching new delta value with, in general case it will not work (in logs I see that timeout kicks in sometimes). At least this prevents form permanent thread blocking and gives a chance to handle somehow this problem:

NSInteger readStreamReadWorkaround(CFReadStreamRef readStrem, UInt8 *buffer, CFIndex bufferLength) {
  static dispatch_once_t onceToken;
  static BOOL isProblematicOs = YES;
  dispatch_once(&onceToken, ^{
    isProblematicOs = [[UIDevice currentDevice].systemName compare: @"7.0" options: NSNumericSearch]!=NSOrderedAscending;
  });

  NSInteger readBytesCount = -2;

  if (isProblematicOs) {
    CFStreamStatus sStatus = CFReadStreamGetStatus(readStrem);
    NSDate *date = [NSDate date];
    while (YES) {
      if(CFReadStreamHasBytesAvailable(readStrem)) {
        readBytesCount = CFReadStreamRead(readStrem, buffer, bufferLength);
        break;
      }
      sStatus = CFReadStreamGetStatus(readStrem);
      if (sStatus!=kCFStreamStatusOpen && sStatus !=kCFStreamStatusAtEnd
          || [date timeIntervalSinceNow]<-15.0) {
        break;
      }
      usleep(50000);
    }
  } else {
    readBytesCount = CFReadStreamRead(readStrem, buffer, sizeof(buffer));
  }
  return readBytesCount;
}

I don't like this solution but so far I don't see an alternative.

Marek R
  • 23,155
  • 5
  • 37
  • 107
  • That's basically the same workaround I'm using, as mentioned in the question. And yeah, it still doesn't always work. – Paul Roub Oct 04 '13 at 13:27