Find matching NSThread for NSRunLoop (needed to fix Socket Rocket) - objective-c

I'm working on a fix for a race condition in Socket Rocket.
This bug was reported a long time ago and is still not fixed.
More than a year ago I've written a fix which breaks API (only shared thread can be used) and this code is running successfully on production (no crashes at all when there are lots of users).
Now I want to tweak my fix in such way that it will not break API of SRWebSocket.
For that, I need to find matching NSThread forgiven NSRunLoop. This is one to one relation, but I have problem finding an API which could help me.
PS. A fix is quite simple. Every operation made on NSRunLoop must be done from a respective thread. There is no NSRunLoop or CFRunLoopAPI that can be safely used from another thread. So I've added such API toSRRunLoopThread`:
- (void)scheduleBlock: (void(^)())block
{
[self performSelector: #selector(_runBlock:)
onThread: self
withObject: [block copy]
waitUntilDone: NO];
}
- (void)_runBlock: (void(^)())block
{
block();
}
and use it in every place where something is done on this NSRunLoop.
This fix shows why I need to find matching NSThread.
Note documentations states that performSelector:onThread:withObject:waitUntilDone: is thread safe
You can use this method to deliver messages to other threads in your application.
I have to stress again that documentation warns clearly that NSRunLoop API is NOT threaded safe:
Warning
The NSRunLoop class is generally not considered to be thread-safe and
its methods should only be called within the context of the current
thread. You should never try to call the methods of a NSRunLoop
object running in a different thread, as doing so might cause
unexpected results.
Since CFRunLoop is just the same thing which can by toil free bridged to/from NSRunLoop, so it has exactly same weaknesses.
So if documentation doesn't say that API it threads safe, then it is not threaded safe and I can't use it in that context (so proposed answer from #DisableR is obviously invalid).

You can perform blocks having a runloop without need of a thread.
Block will be performed asynchronously on a thread that is associated with the run loop.
CFRunLoopPerformBlock([myNSRunLoop getCFRunLoop], kCFRunLoopCommonModes, block);
CFRunLoopWakeUp([myNSRunLoop getCFRunLoop]);
Here is a discussion about implementation of -performSelectorOnMainThread: method of NSThread on macOS Tiger where it was not available, the problem that is quite similar to yours:
http://www.cocoabuilder.com/archive/cocoa/112261-cfrunlooptimer-firing-delay.html
Verifying the thread safety:
import Foundation
var runLoopFromThread: CFRunLoop!
let lock = NSLock()
lock.lock()
let thread = Thread {
autoreleasepool {
runLoopFromThread = CFRunLoopGetCurrent()
lock.unlock()
while true {
autoreleasepool {
CFRunLoopRun()
}
}
}
}
thread.start()
lock.lock()
let runLoop = runLoopFromThread
lock.unlock()
let racingLock = NSLock()
for _ in 0..<10000 {
DispatchQueue.global().async {
let acquired = racingLock.try()
CFRunLoopPerformBlock(runLoop, CFRunLoopMode.commonModes.rawValue) {
var i = 0
i += 1
}
CFRunLoopWakeUp(runLoop);
if acquired {
racingLock.unlock()
} else {
print("Potential situation in which issues with thread safety may arise. No crash / thread sanitizer warnings means thread safety is present.")
}
}
}
dispatchMain()

Related

How to not crash your app on quit when using concurrency

I'm using NSOperationQueue and a subclass of NSOperation for a part in my app that is generating a lot of data and therefore is very calculation-heavy.
When the app is closed by the user processingQueue.cancelAllOperations() is called. Also in my NSOperation subclass I overwrote cancel() to let it forward a cancel request to the class that does the actual heavy lifting ...
override func cancel() {
AppDelegate.instance.cancelDataGeneration()
super.cancel()
}
But this is still not enough. When I close the app while the data generation is ongoing it will crash in Xcode.
What can I do to prevent the crashing (which might result in data loss)? Is it OK to let the app wait for closing until all concurrent operations are canceled and how is this done (if it's even possible)? Or what other methods are generally used to address this issue?
UPDATE:
After more investigation I found that cancel() on my NSOperation subclass is never called, even after calling processingQueue.cancelAllOperations() in applicationShouldTerminate. So I added a method to manually call cancel on it:
func cancelDataGeneration() {
if let op = AppDelegate.instance._operation {
op.cancel();
}
}
And I call this from inside applicationShouldTerminate (since applicationShouldTerminate is called earlier than applicationWillTerminate. Interestingly, since my AppDelegate is a Singleton I have to use AppDelegate.instance._operation. If I only check for _operation it results in being nil when called from applicationShouldTerminate. Would be interesting to know why this is the case.
In any case, canceling now works properly: When the app is quit, it will cancel the data generation class and exits without crashing ... mostly anyway. But I still wonder why my NSOperation subclass' cancel() isn't called when I use processingQueue.cancelAllOperations()!
From Apple's documentation.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state.
I would block the App's mainthread until the NSOperationQueue finishes all of its work.
I would call [NSOperationQueue cancelAllOperations] first.
Then in the 'Application will terminate' method I will call
[NSOperationQueue waitUntilAllOperationsAreFinished]. This will make sure the currently executing block(all the other queued tasks will be cancelled) will complete before the application quits.
Now if you are not comfortable with the main thread blocking until the currently executing block finishes, then you need to check for a flag(or an NSApplicationDelegate could be set on that class) which signals if the application is still active in order to continue. If application is to be terminated, then fall out of the block voluntarily, this is the cleanest way to do it.
Something roughly like the below.
void ^(aBlock)(void) = ^void(void)
{
for(NSUInteger i = 0;i < 1000; i++)
{
// heavy processing code. Taking several iterations each second
// At the start of each iteration, check for a flag, to see if to quit
if(_applicationIsShuttingDown) break;
// perform block operation
}
};
and your class is an NSApplicationDelegate and implements
-applicationWillTerminate:(NSNotification *)aNotification
{
_applicationIsShuttingDown = YES;
}

Threaded Obj-C code with ARC enabled -- why it works this way?

I need an extra thread in background to listen to requests from socket.
The code is put into a singleton class; it will be called in main.m before NSApplicationMain() like this:
[[SKSocketThread getSingleton] runThread];
And runThread is defined as follow:
- (void) runThread {
[NSThread detachNewThreadSelector:#selector(socketThreadMainLoop:)
toTarget:self
withObject:[self quitLock]];
}
- (void) socketThreadMainLoop:(id)param {
NSLock *lock = (NSLock *)param;
while (![lock tryLock]) {
NSLog(#"Yay! We are in socketThreadMainLoop now!");
[NSThread sleepForTimeInterval:2];
}
NSLog(#"Terminating the socket thread...");
[lock unlock]; // is it really necessary?
}
It compiled successfully with no warning, but will throw an error in runtime:
autoreleased with no pool in place.
I did some googling, tried to pack code in runThread and socketThreadMainLoop with #autoreleasepool, but the error is still there. Finally I wrapped call to runThread with it in main.m, and that worked!
I don't know why it only works this way...
You should wrap your code with #autoreleasepool block.
...
- (void) socketThreadMainLoop:(id)param {
#autoreleasepool
{
NSLock *lock = (NSLock *)param;
while (![lock tryLock]) {
NSLog(#"Yay! We are in socketThreadMainLoop now!");
[NSThread sleepForTimeInterval:2];
}
NSLog(#"Terminating the socket thread...");
[lock unlock]; // is it really necessary?
}
}
Read more:
NSAutoreleasePool Class Reference
Set a breakpoint on objc_autoreleaseNoPool and post the backtrace. You need an #autoreleasepool{...} in all threads that don't us run loops, including the main thread (in your main.m, if you aren't calling into NSApplicationMain()).
Some additional feedback; that you named the method getSingleton indicates that you are new to iOS development (don't name methods get* anything). That you are using sleep in a while loop indicates that you are a bit new to the whole networking thing, too.
Also, spinning up a thread prior to the call into NSApplicationMain() is totally the wrong thing to do; you should be doing the networking goop as a normal part of application startup... see below.
You really really really don't want to do networking using a handrolled while() loop with sleep. Polling is an awful pattern on mobile devices; it is battery hungry and that sleep is just going to make things unresponsive.
Use a proper run loop and/or dispatch sources and/or CFStream APIs and/or NSFileHandles.

Locked up waiting for #synchronized

I have this (rare) odd case where my objective-c iOS program is locking up. When I break into the debugger, there are two threads and both of them are stuck at a #synchronized().
Unless I am completely misunderstanding #synchronized, I didn't think that was possible and the whole point of the command.
I have a main thread and worker thread that both need access to a sqlite database, so I wrap the chunks of code that are accessing the db in #synchronized(myDatabase) blocks. Not much else happens in these blocks except the db access.
I'm also using the FMDatabase framework to access sqlite, I don't know if that matters.
The myDatabase is a global variable that contains the FMDatabase object. It is created once at the start of the program.
I know I'm late to the party with this, but I've found a strange combination of circumstances that #synchronized handles poorly and is probably responsible for your problem. I don't have a solution to it, besides to change the code to eliminate the cause once you know what it is.
I will be using this code below to demonstrate.
- (int)getNumberEight {
#synchronized(_lockObject) {
// Point A
return 8;
}
}
- (void)printEight {
#synchronized(_lockObject) {
// Point B
NSLog(#"%d", [self getNumberEight]);
}
}
- (void)printSomethingElse {
#synchronized(_lockObject) {
// Point C
NSLog(#"Something Else.");
}
}
Generally, #synchronized is a recursively-safe lock. As such, calling [self printEight] is ok and will not cause deadlocks. What I've found is an exception to that rule. The following series of events will cause deadlock and is extremely difficult to track down.
Thread 1 enters -printEight and acquires the lock.
Thread 2 enters -printSomethingElse and attempts to acquire the lock. The lock is held by Thread 1, so it is enqueued to wait until the lock is available and blocks.
Thread 1 enter -getNumberEight and attempts to acquire the lock. The lock is held already and someone else is in the queue to get it next, so Thread 1 blocks. Deadlock.
It appears that this functionality is an unintended consequence of the desire to bound starvation when using #synchronized. The lock is only recursively safe when no other thread is waiting for it.
The next time you hit deadlock in your code, examine the call stacks on each thread to see if either of the deadlocked threads already holds the lock. In the sample code above, by adding long sleeps at Point A, B, and C, the deadlock can be recreated with almost 100% consistency.
EDIT:
I'm no longer able to demonstrate the previous issue, but there is a related situation that still causes issues. It has to do with the dynamic behavior of dispatch_sync.
In this code, there are two attempts to acquire the lock recursively. The first calls from the main queue into a background queue. The second calls from the background queue into the main queue.
What causes the difference in behavior is the distinction between dispatch queues and threads. The first example calls onto a different queue, but never changes threads, so the recursive mutex is acquired. The second changes threads when it changes queues, so the recursive mutex cannot be acquired.
To emphasize, this functionality is by design, but it behavior may be unexpected to some that do not understand GCD as well as they could.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSObject *lock = [[NSObject alloc] init];
NSTimeInterval delay = 5;
NSLog(#"Example 1:");
dispatch_async(queue, ^{
NSLog(#"Starting %d seconds of runloop for example 1.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(#"Finished executing runloop for example 1.");
});
NSLog(#"Acquiring initial Lock.");
#synchronized(lock) {
NSLog(#"Acquiring recursive Lock.");
dispatch_sync(queue, ^{
NSLog(#"Deadlock?");
#synchronized(lock) {
NSLog(#"No Deadlock!");
}
});
}
NSLog(#"\n\nSleeping to clean up.\n\n");
sleep(delay);
NSLog(#"Example 2:");
dispatch_async(queue, ^{
NSLog(#"Acquiring initial Lock.");
#synchronized(lock) {
NSLog(#"Acquiring recursive Lock.");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Deadlock?");
#synchronized(lock) {
NSLog(#"Deadlock!");
}
});
}
});
NSLog(#"Starting %d seconds of runloop for example 2.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(#"Finished executing runloop for example 2.");
I stumbled into this recently, assuming that #synchronized(_dataLock) does what it's supposed to do, since it is such a fundamental thing after all.
I went on investigating the _dataLock object, in my design I have several Database objects that will do their locking independently so I was simply creating _dataLock = [[NSNumber numberWithInt:1] retain] for each instance of Database.
However the [NSNumber numberWithInt:1] returns the same object, as in same pointer!!!
Which means what I thought was a localized lock for only one instance of Database is not a global lock for all instances of Database.
Of course this was never the intended design and I am sure this was the cause of issues.
I will change the
_dataLock = [[NSNumber numberWithInt:1] retain]
with
_dataLock = [[NSUUID UUID] UUIDString] retain]

Protecting critical code from being called again

I need to protect a critical area of my code, which is multi-threaded. I want to prevent it from being called multiple times before the other thread is finished. This is what I am working with:
- (void) filterAllEventsIntoDictionary{
// start critical area
if (self.sortedKeys.count != 0) {
[self.sortedKeys removeAllObjects];
}
dispatch_async(self.filterMainQueue, ^{
[self internal_filterAllEventsIntoDictionary];
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
}
Since the internal_filterAllEventsIntoDictionary method also accesses self.sortedKeys, if this code is called twice, it crashes because of removeAllObjects at the start.
I still need to call the internal... method in another thread since I don't want to block the UI. So what's the best way to block on the start of this method while the dispatch_async call is still not finished?
While I am far from being a concurrency expert, it sounds to me like you need a lock on your sortedKeys object. If you used a traditional lock, though, you'd end up blocking the main thread.
The recommended replacement for locks in the world of Grand Central Dispatch is to put critical sections of code on a serial queue. See "Eliminating Lock-Based Code" in the Concurrency Programming Guide.
If you put the [self.sortedKeys removeAllObjects]; call onto the same queue that the block with the internal... call is scheduled on, you guarantee that it won't happen until after that block completes:
// start critical area
dispatch_async(self.filterMainQueue, ^{
if (self.sortedKeys.count != 0) {
[self.sortedKeys removeAllObjects];
}
});
This assumes that filterMainQueue is serial. Using dispatch_async for the critical section ensures that the main thread will not be blocked. Also note the warning in "Dispatch Queues and Thread Safety":
Do not call the dispatch_sync function from a task that is executing on the same queue that you pass to your function call. Doing so will deadlock the queue.
Although this will only be an issue if the internal... method does something that causes this method to be called again.

terminating a secondary thread from the main thread (cocoa)

I'm working on a small app written in objective-c with the help of the cocoa framework and I am having a multithreading issue.
I would really appreciate it if somebody could help me with some guidance on how terminate a secondary(worker) thread from the main thread?
- (IBAction)startWorking:(id)sender {
[NSThread detachNewThreadSelector:#selector(threadMain:) toTarget:self withObject:nil];
}
- (void)threadMain
{
// do a lot of boring, time consuming I/O here..
}
- (IBAction)stop:(id)sender {
// what now?
}
I've found something on apple's docs but what is missing from this example is the part where the runloop input source changes the exitNow value.
Also, I won't be using many threads in my app so I would prefer a simple solution (with less overhead) rather than a more complex one that is able to manage many threads easily, but with more overhead generated (eg. using locks maybe(?) instead of runloops)
Thanks in advance
I think the easiest way is to use NSThread's -(void)cancel method. You'll need a reference to the thread you've created, as well. Your example code would look something like this, if you can do the worker thread as a loop:
- (IBAction)startWorking:(id)sender {
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[myThread start];
}
- (void)threadMain
{
while(1)
{
// do IO here
if([[NSThread currentThread] isCancelled])
break;
}
}
- (IBAction)stop:(id)sender {
[myThread cancel];
[myThread release];
myThread = nil;
}
Of course, this will only cancel the thread between loop iterations. So, if you're doing some long blocking computation, you'll have to find a way to break it up into pieces so you can check isCancelled periodically.
Also take a look at the NSOperation and NSOperationQueue classes. It's another set of threading classes that make developing a worker thread model very easy to do.