How should you handle the possibility of an NSStream blockage? - objective-c

Per Apple’s “Polling Versus Run-Loop Scheduling”:
[hasSpace/BytesAvailable] can mean that there is available bytes or space or that the only way to find out is to attempt a read or a write operation (which could lead to a momentary block).
The doc does not explicitly state that hasSpace/BytesAvailable events behave the same way, only, obscurely, that they have “identical semantics."
Am I to conclude that a write/read streamError or a bytes read/written return of less than the amount expected could be due to a “momentary block”?
If so, should I attempt the transmission again? Should I use some sort of timer mechanism to give the blockage a chance to clear? This would be a lot of work to implement, so I’d rather not if it’s unlikely to help.
(It’s tempting to initiate a limited polling loop in such a case, say a while loop that makes 10 attempts, but I don’t know if it’s safe to do that at the same time as the stream is scheduled in the run loop, and I have no way to test it.)

Here is a good wrapper for sockets: https://github.com/robbiehanson/CocoaAsyncSocket
It will queue reads and writes if the connection is not available. You don't mention if you're using UDP or TCP, however I suspect you're using TCP, in which case it will handle any interruptions on its own -- provided the connection doesn't get torn down.

It’s been a long haul. Here’s some followup on this issue:
Early on, I threw out the idea of maintaining and checking a leftover cache because that would have worked only for the output stream, when further reflection suggested that the input stream could also become blocked.
Instead, I set up idling while-loops:
- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode)
// RECEIVING
case NSStreamEventHasBytesAvailable: {
if (self.receiveStage == kNothingToReceive)
return;
// Get the data from the stream. (This method returns NO if bytesRead < 1.)
if (![self receiveDataViaStream:(NSInputStream *)theStream]) {
// If nothing was actually read, consider the stream to be idling.
self.bStreamIn_isIdling = YES;
// Repeatedly retry read, until (1) the read is successful, or (2) stopNetwork is called, which will clear the idler.
// (Just in case, add nil stream property as a loop breaker.)
while (self.bStreamIn_isIdling && self.streamIn) {
if ([self receiveDataViaStream:(NSInputStream *)theStream]) {
self.bStreamIn_isIdling = NO;
// The stream will have started up again; prepare for next event call.
[self assessTransmissionStage_uponReadSuccess];
}
}
}
else
// Prepare for what happens next.
[self assessTransmissionStage_uponReadSuccess];
break;
// SENDING
case NSStreamEventHasSpaceAvailable:
if (self.sendStage == kNothingToSend)
return;
if (![self sendDataViaStream:(NSOutputStream *)theStream]) {
self.bStreamOut_isIdling = YES;
while (self.bStreamOut_isIdling && self.streamOut) {
if ([self sendDataViaStream:(NSOutputStream *)theStream]) {
self.bStreamOut_isIdling = NO;
[self assessTransmissionStage_uponWriteSuccess];
}
}
}
else
[self assessTransmissionStage_uponWriteSuccess];
break;
// other event cases…
Then it came time to test a user-initiated cancellation, via a “cancel” button. Midway through the sync, there’s a pause on the Cocoa side, awaiting user input. If the user cancels at this point, the Cocoa app closes the streams and removes them from the runloop, so I expected that the streams on the other side of the connection would generate NSStreamEventEndEncountered events, or perhaps NSStreamEventErrorOccurred. But, no, only one event came through, an NSStreamEventHasBytesAvailable! Go figure.
Of course, there weren’t really any “bytes available,” as the stream had been closed on the Cocoa side, not written to — so the stream handler on the iOS side went into an infinite loop. Not so good.
Next I tested what would happen if one of the devices went to sleep. During the pause for user input, I let the iPhone sleep via auto-lock*, and then supplied the user input on the Cocoa side. Surprise again: the Cocoa app continued without perturbation to the end of the sync, and when I woke up the iPhone, the iOS app proved to have completed its side of the sync too.
Could there have been a hiccup on the iPhone side that was fixed by my idle loop? I threw in a stop-network routine to check:
if (![self receiveDataViaStream:(NSInputStream *)theStream])
[self stopNetwork]; // closes the streams, etc.
The sync still ran through to completion. There was no hiccup.
Finally, I tested what happened if the Mac (the Cocoa side) went to sleep during that pause for input. This produced a sort of backward belch: Two NSStreamEventErrorOccurred events were received — on the Mac side, after which it was no longer possible to write to the output stream. No events at all were received on the iPhone side, but if I tested the iPhone's stream status, it would return 5, NSStreamStatusAtEnd.
CONCLUSIONS & PLAN:
The "temporary block" is something of a unicorn. Either the network runs smoothly or it disconnects altogether.
If there is truly such a thing as a temporary block, there is no way to distinguish it from a complete disconnection. The only stream-status constants that seem logical for a temporary block are are NSStreamStatusAtEnd and NSStreamStatusError. But per the above experiments, these indicate disconnection.
As a result of which I’m discarding the while-loops and am detecting disconnection solely by checking for bytesRead/Written < 1.
*The iPhone won’t ever sleep if it’s slaved to Xcode. You have to run it straight from the iPhone.

You can anticipate "disconnection" whenever you attempt to write 0 bytes to the output stream, or when you receive 0 bytes on the input stream. If you want to keep the streams alive, make sure you check the length of bytes you're writing to the output stream. That way, the input stream never receives 0 bytes, which triggers the event handler for closed streams.
There's no such thing as an "idling" output stream. Only an idling provider of bytes to the output stream, which doesn't need to indicate its idleness.
If you're getting disconnected from your network connection by the sleep timer, you can disable that when you open your streams, and then disable it when you close them:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventOpenCompleted:
{
[UIApplication sharedApplication].idleTimerDisabled = YES;
break;
}
case NSStreamEventEndEncountered:
{
[UIApplication sharedApplication].idleTimerDisabled = NO;
break;
}
}
}
I wouldn't delve any further into the specifics of your situation because I can tell right-off-the-bat that you aren't completely clear on what streams are, exactly. I understand that the documentation on streams is really poor at priming newbies, and is scant, to-boot; but, they model the same streams that have been around for 30 years, so any documentation on streams for any operating system (except Windows) will work perfectly at bringing you up to speed.
By the way, the other, inextricable part of streams is your network connection code, which you did not supply. I suggest that, if you're not already using NSNetService and NSNetServiceBrowser to find peers, connect to them, and acquire your streams accordingly, you should. Doing so allows you to easily monitor the state of your network connection, and quickly and easily reopen your streams should they closed unexpectedly.
I have very thorough, yet easy-to-follow sample code for this, which would require no customization on your end at all to use, if anyone would like it.

Related

How to detect network disconnects (on an RTCPeerConnection) as soon as possible or the resulting frozen video?

I am using RTCPeerconnections to submit video and audio in a web-RTC-based video-messenger. I am able to detect network disconnects after approximately 7 seconds - yet, this is 7 seconds in which the user is staring at a frozen video and starts to randomly click buttons in the app. I would like to improve the user experience by shortening this timespan - e.g. by informing the user about a network issue if the video freezes for more than 1 second.
Status Quo: I am currently detecting respective situations by listening to the onconnectionstatechange event of the RTCPeerConnection. Yet, the event is only fired approximately 7 seconds after the disconnect. I determined the ~7 seconds by connecting two machines via normal WiFi, using a hardware switch on one of the laptops to switch off the wireless (such switches exist on some older Lenovo models / guarantee an immediate disconnect) and wait for the other machine to detect the event.
Consideration: The root cause being the interruption of the underlying network connection, it would be ideal to detect the changed network status as early as possible (even if its just transport delays). This said, the disturbance faced by the user ultimately stems from the video that instantly freezes when interrupting the network. If there was no way to detect the connection issue earlier, it could be an option to detect the frozen video instead. Is any of these two things possible (ideally event-driven, so that I don't need to poll things every second)?
Here's a very simple code snippet describing my current disconnect detection:
myRTCPeerConnection.onconnectionstatechange = (event: Event) => {
let newCS = myRTCPeerConnection.connectionState;
if (newCS == "disconnected" || newCS == "failed" || newCS == "closed") {
//do something on disconnect - e.g. show messages to user and start reconnect
}
}
(ice)connectionstatechange is the right event in general.
If you want more granularity you'll need to poll getStats and looks for stats like framesReceived. But there is no guaranteed frame rate sent from the other side (e.g. in screensharing you go below 1/s).
While the actual ICE statistics like requestsSent seem more useful they happen much less frequently, only once per second and you can loose a packet or it comes late.
In general this is a question of how reliable the detection of the network failure is. If it is too aggressive you end up with a poor UX showing a warning too often.
You might not end up that is significantly better than at the cost of introducing complexity that you need to maintain.
Thanks Philipp for your insights - this pointed me into the right direction.
I'm now looking into using getStats to identify any freezes. At first sight, polling the framesPerSecond value seems most promising to me. The good thing: it reacts instantly upon disconnect - and - it still works when the underlying video stream is paused (i'm allowing the user to pause video submission / implemented it by setting all video tracks to enabled = false). I.e. even if the video tracks are disabled on the sending side, the receiving side still continues to receive the agreed frames per second.
As the usage of the getStats function appears weak on documentation at the time of this being written / there's rarely a simple examples for its usage, please find my code extract below:
peerRTCPC
.getReceivers()
.forEach(
(
receiver: RTCRtpReceiver,
index: number,
array: RTCRtpReceiver[]
) => {
if (receiver.track.kind == "video") {
receiver.getStats().then((myStatsReport: RTCStatsReport) => {
myStatsReport.forEach(
(statValue: any, key: string, parent: RTCStatsReport) => {
if (statValue.type == "inbound-rtp") {
console.log(
"The PC stats returned the framesPerSecond value " +
statValue["framesPerSecond"] +
" while the full inbound-rtp stats reflect as " +
JSON.stringify(statValue)
);
}
}
);
});
}
}
);
Note that upon disconnect, the framesPerSecond do not necessarily go to zero, even though the webRTCInternals screen suggests the same. I am seeing undefined when a disconnect happens.
Runtime impact of polling this at high frequency / across larger numbers of connections probably needs to be looked at more closely. Yet, this seems like a good step into the right direction unless doing it way to frequently.

How do I call some blocking method with a timeout in Obj-C?

Is there a standard nice way to call a blocking method with a timeout in Objective C? I want to be able to do:
// call [something blockingMethod];
// if it hasn't come back within 2 seconds, forget it
Thanks.
It is not possible to interrupt a function that is not designed to be interrupted. Doing so would generally cause data corruption and resource leaks.
The standard way to achieve what you're describing is to redesign blockingMethod so that it accepts a timeout or other cancelation mechanism.
If that's not possible, and it is required that you timeout blockingMethod, the standard approach is to fork a child process to run blockingMethod, and kill it (usually by sending SIGTERM) if it doesn't finish by the timeout. This is somewhat complex to implement in ObjC, and you'll need to also implement a mechanism to send the results back to the parent process. Since the operating system manages resources (memory, file handles, etc) at the process level, the only way to forcibly interrupt a function is to create a separate process for it. This still can lead to data corruption depending on what blockingMethod does, but it will work for a much larger set of problems.
Note that it's not generally possible to fork a process from non-Apple code on iOS, so this can't be done there.
As an example of what I mean by "data corruption," consider some simple code like:
[self.cache lock];
[self.cache removeObject: object];
[self.cache decrementCountOfObjects];
[self.cache unlock];
Now imagine that the process were forcibly terminated in the middle of this operation. What should happen? How does the cache get unlocked? How are the cache contents and the count reconciled? It's even possible that the object would be in the middle of being copied; then what? How would the system automatically deal with all of these issues unless blockingMethod were written with cancelation in mind?
How about using a semaphore? This can be locked across threads and then you can do something like
dispatch_semaphore_t s = dispatch_semaphore_create ( 0 );
// In a different thread or on some queue,
// fire up some process, when done signal
// the semaphore with
[ fire up thread ... some task, when done
dispatch_semaphore_signal( s );
... ]
// This waits 2 seconds for the semaphore
if ( dispatch_semaphore_wait( s, 2 ) )
{
// ... it hasn't come back after 2 seconds so 'forget it'
}
else
{
// ... you now have the semaphore within 2 seconds so 'do it'
}
// This waits forever, just for reference
dispatch_semaphore_wait( s, DISPATCH_TIME_FOREVER );

Need to CFRunLoopRun() but want it unblocking

I have the following piece of code in a cocoa Application for OSX:
void *callbackInfo = NULL; // could put stream-specific data here.
FSEventStreamRef stream;
CFAbsoluteTime latency = 1.0; /* Latency in seconds */
/* Create the stream, passing in a callback */
stream = FSEventStreamCreate(NULL,
&mycallback,
callbackInfo,
pathsToWatch,
kFSEventStreamEventIdSinceNow, /* Or a previous event ID */
latency,
kFSEventStreamCreateFlagFileEvents//kFSEventStreamCreateFlagNone /* Flags explained in reference */
);
/* Create the stream before calling this. */
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(),kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
CFRunLoopRun();
}
This code gets notified about files system changes and reacts accordingly (the callback function is not in the snippet).
My problem now is that the CFRunLoopRun() is blocking. I.e. the further execution of the code stops. However, I'm looking for a possibility that I can start the observation of file system changes but also stop it again (e.g. from another object).
One option that I thought of would be to only start the loop for a second and check for a global variable afterwards. However, I usually don't like global variables....
Does anybody here have a nice and handy idea, how this could be solved? Would it be a good idea to buy the overhead and put the execution into a separate thread?
Thanks in advance!
Norbert
CFRunLoopRun() runs the current run loop forever, until somebody registered in the runloop calls CFRunLoopStop(). This is not what you want to do unless you're doing something very fancy.
If you want to the FSEventStream callback to run, you just register it with the runloop and leave it, you don't have to do anything explicitly to the runloop after that, registering the even stream as a source is all you have to do.
If you want to stop observing the stream you call FSEventStreamStop() on it.
A Cocoa application has an implicit run loop, so just delete CFRunLoopRun();
Consider to use a dispatch queue via Grand Central Dispatch (GCD) rather than schedule the stream on a run loop

Can an open, but inactive, NSStream that is scheduled on the main thread be moved to a different thread?

I am using (and am required to use) a third-party framework to which I do not have source. The third-party framework handles creating an authenticated client/server connection and hands back a pair of open NSStreams. The challenge that I have is the NSStreams are scheduled on the main thread (creating situations where the UI may become unresponsive - which I would like to avoid).
At the point that the streams are handed off from the third party framework, no network traffic is in progress. So, I am wondering if I could just unschedule and reschedule the NSStreams.
Does anyone know if it is possible to unschedule an open NSStream and reschedule it on a different run loop on a different thread? Will that cause problems? Are there any code examples out there?
Thanks in advance!
Aaron
If I understand your application correctly, it means that your application receives references to a particular stream, and you are in charge of reading everything on the steams. Reading these streams should be something that you force into the background from your application via a NSThread, NSOperation or other threading mechanism.
Example:
In whatever file your tieing in this NSInputStream:
#property (strong, nonatomic) NSInvocationOperation *parseOp;
(id)startInputRead:(NSInputStream *)input {
if([input hasBytesAvailable]) {
self.parseOp = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(readAndStoreInput:) object:inputStream];
}
Where your reader is something like:
(void)readAndStoreInput:(NSInputSteam*) input{
//process your input steam into your system in the background
}
This is a short example of just how you would do this for the input side. You could also queue up work for the output steam in a similar fashion. This should make everything run concurrently and your app stay responsive.

Cancel thread with read() operation on serial port

in my Cocoa project, I communicate with a device connected to a serial port. Now, I am waiting for the serial device to send a particular message of some bytes. For the read operation (and the reaction for once the desired message has been received), I created a new thread. On user request, I want to be able to cancel the thread.
As Apple suggests in the docs, I added a flag to the thread dictionary, periodically check if the flag has been set and if so, call [NSThread exit]. This works fine.
Now, the thread may be stuck waiting for the serial device to finally send the 12 byte message. The read call looks like this:
numBytes = read(fileDescriptor, buffer, 12);
Once the thread starts reading from the device, but no data comes in, I can set the flag to tell the thread to finish, but the thread is not going to read the flag unless it finally received at least 12 bytes of data and continues processing.
Is there a way to kill a thread that currently performs a read operation on a serial device?
Edit for clarification:
I do not insist in creating a separate thread for the I/O operations with the serial device. If there is a way to encapsulate the operations such that I am able to "kill" them if the user presses a cancel button, I am perfectly happy.
I am developing a Cocoa application for desktop Mac OS X, so no restrictions regarding mobile devices and their capabilities apply.
A workaround would be to make the read function return immediately if there are no bytes to read. How can I do this?
Use select or poll with a timeout to detect when the descriptor is ready for reading.
Set the timeout to (say) half a second and call it in a loop while checking to see if your thread should exit.
Asynchronous thread cancellation is almost always a bad idea. Try to stick with event-driven interfaces (and, if necessary, timeouts).
This is exactly what the pthread_cancel interface was designed for. You'll want to wrap the block with read in pthread_cleanup_push and pthread_cleanup_pop in order that you can safely clean up if the thread is cancelled, and also disable cancellation (with pthread_setcancelstate) in other code that runs in this thread that you don't want to be cancellable. This can be a pain if proper cleanup would involve multiple call frames; it essentially forces you to use pthread_cleanup_push at every call level and structure your thread code like C++ or Java with try/catch style exception handling.
An alternative approach would be to install a signal handler for an otherwise-unused signal (like SIGUSR1 or one of the realtime signals) without the SA_RESTART flag, so that it interrupts syscalls with EINTR. The signal handler itself can be a complete no-op; the only purpose of it is to interrupt things. Then you can use pthread_kill to interrupt the read (or any other syscall) in a particular thread. This has the advantage that you don't have to switch your code to using C++/Java-type idioms. You can handle the EINTR error by checking a flag (indicating whether the thread was requested to abort) and resume the read if the flag is not set, or return an error code that causes the caller to clean up and eventually pthread_exit.
If you do use interrupting signal handlers, make sure all your syscalls that can return EINTR are wrapped in loops that retry (or check the abort flag and optionally retry) on EINTR. Otherwise things can break badly.