Stairways Software Icon

Stairways Software

Excellence In Software For Over Twenty Years – Only with a Mac

NSAppleScript is Really Not Thread Safe

Jeff Johnson recently posted an article on NSNotificationCenter not really being thread safe, and in a similar vein, this article is to warn you that NSAppleScript is often not safe even on the main thread if you have other things happening in your application.

It's well known that NSAppleScript is not thread safe, but in fact its mostly not safe at all even on the main thread is probably not as well known as it should be. The problem is that [NSAppleScript executeAndReturnError] appears to be a blocking call, and that calling it on the main thread should be safe enough as long as you're careful to ensure the AppleScript is going to finish quickly.

Unfortunately, the problem is that its not really blocking - sure, this part of your main thread stops, but other things can run on your main thread while you're blocked here, which can easily violate a whole lot of assumptions.

For example, say you are on the main thread, and you start a "transaction", make the first part of a change, call [NSAppleScript executeAndReturnError], and then finish the change and end the "transaction". It appears safe, but [NSAppleScript executeAndReturnError] allows other main thread activity to happen such as timers and event processing, and so if you're doing all your transactions "safely" on the main thread, suddenly all those main thread elements have to deal with the potential of a transaction in progress. Yuck!

Here is a simple example:

- (void) logit;
{
 NSLog( @"performSelector:afterDelay: ran" );
}

- (void) test;
{
 NSLog( @"Start Test" );
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  NSLog( @"dispatch_after ran" );
 });
 [self performSelector:@selector(logit) withObject:nil afterDelay:0.1];
 (void)[[[NSAppleScript alloc] initWithSource:@"delay 1"] executeAndReturnError:NULL];
 NSLog( @"Finish Test" );
}

All the code runs on the main thread, so you would expect the Start Test and Finish Test to appear first, and then the logit method and the dispatch_after block to run. But in fact both of those are executed before the Finish Test.

So basically the only safe way to use [NSAppleScript executeAndReturnError] (and probably most of anything else in NSAppleScript) is in a block on its own on the main thread. Essentially:

dispatch_async(dispatch_get_main_queue(), ^{
 (void)[[[NSAppleScript alloc] initWithSource:@"delay 1"] executeAndReturnError:NULL];
});

Otherwise, if you're doing anything before or after the NSAppleScript calls, you have to assume lots of other parts of your code could run in between which is almost impossible to ensure correctness.

What an ugly mess!

Posted Thursday, April 24, 2014. Permalink. Post a Comment.

Comments

Post Comment

Name: (optional)
URL: (optional)
Email: (optional, not published)
   
CAPTCHA: eight two six three (required)
  To prove you are human, please enter the number as digits (ie, 1-9).
Comment:
  You can use some HTML tags such as <b>, <i>, and <a href="">.
We reserve the right to remove any offensive or inappropriate comments.
Due to spam issues, comments are initially invisible until we review them,
you can see them (background red) and we can see them, but no one else.

Comment Preview

None yet.

Buy Now

User Database

Stairways