Why does my app stall for five seconds when it handles `application:openFiles:`?

For performance testing and improving my Find Any File app, when it sends tens of thousands of file references to an app via its “Pass Results” rule, I was attempting to make a simple app in Xcode that implements the application:openFiles: handler and that logs the number of items it has received.

And that works.

However, the app (or script) that passes the file references to the app’s “open” (odoc) handler, will always be stalled for 5 seconds during this invocation.

Here’s how I made the app:

Create a new Mac “App” project (ObjC) in Xcode, and implement in its AppDelegate.m:

-(void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
	NSLog(@"open %ld files", filenames.count);
	[NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}

I run the app (I called it “Test.app”), which will open an empty default window, and then I invoke its “open” handler, either from FAF or via a simple AppleScript like this:

tell application "Test.app"
	open {"/etc/hosts"}
end tell

The app’s openFiles: handler gets invoked immediately, adding a line to the log. But the script then keeps waiting until 5s have passed. Every time.

I also tried this with openURLs: instead. Same result.

Any idea what’s causing the delay?

BTW, I later realized that I can achieve my goal also with a simple AppleScript like this:

on open theFiles
	display notification "open count: " & (count of theFiles)
end open

on run
	delay 1
end run

But I’d still like to understand what’s causing the 5s delay in the ObjC project.

Have you checked whether the delay is before or after the call to replyToOpenOrPrint:?

The delay comes after the call to replyToOpenOrPrint:.

Also: In the case of instead implementing application:openURLs:, which doesn’t require such a call (but I need to add a Doc Type for it in the Info.plist), I get the same result.

So the delay comes after the handler’s invocation, i.e. it seems out of my hands.

And I can reproduce it on two different Macs (macOS 12.5.1 and macOS 10.13)

Test project: http://files.tempel.org/tmp/odoc_5s_stall.zip

Just run it, and drop a file from Finder on it - you WON’T get a stall. But when you do the same from an AppleScript, as shown above, then you’ll get the 5s stall. It makes no sense to me.

Where are you actually measuring the stall?

I added a custom NSApplication class and overrode replyToOpenOrPrint: like this:

- (void)replyToOpenOrPrint:(NSApplicationDelegateReply)reply {
    NSLog(@"Before");
    [super replyToOpenOrPrint:reply];
    NSLog(@"After");
}

And I don’t see any delay there.

I looked at this again, and I think the problem is that you’re returning NSApplicationDelegateReplySuccess. This tells Cocoa scripting to return a list of object references to the opened documents, and that fails because you don’t actually open them. Try returning NSApplicationDelegateReplyCancel instead.

Alternatively, don’t call replyToOpenOrPrint: at all, and instead. Just use something like this:

NSScriptCommand* currentCommand = [NSScriptCommand currentCommand];
if (currentCommand) {
	[currentCommand suspendExecution];
	[currentCommand performSelector:@selector(resumeExecutionWithResult:) withObject:nil afterDelay:0.0];
}

Shane, many thanks for your efforts to help me here.

However, I think that replyToOpenOrPrint: is irrelevant, because when I instead use application:openURLs:, then there’s no need to call that function, even, yet the result is the same.

Returning NSApplicationDelegateReplyCancel instead also doesn’t do anything about the delay, but then the script I’m running in SD will show an error after 5s: “Test got an error: User canceled.”

Your other suggested work-around also has no effect in my test because currentCommand returns nil.

Have you even been able to reproduce the delay I am seeing? Since I get it on two different systems, I assume it’s not unique to my setup. Basically, I run the Xcode project first, then, in Finder, option-Copy the path to the running app, and then to to S.D., where I modify the proposed script, replacing “Test.app” with the path I just copied. Then I run the script in SD, which then immediately shows the NSLog entries in Xcode’s output pane, whereas SD keep showing that the script is still running until 5s have passed. So, it’s the script that keeps waiting for a response, and I don’t understand how to fix that.

It should return the current command if you’re addressing it via script.

Believe it or not, I went and checked some old code and realised we hit the problem in Script Debugger itself – there are old posts complaining about the delay around here somewhere. The workaround I posted is what we use. I just checked the code, which has the comment:

// suspend and resume removes long delay

The only difference is that we build our own array of object references to pass to resumeExecutionWithResult:. You might try an empty array, to see if that makes a difference.

I can’t use resumeExecutionWithResult: because, as I wrote, currentCommand gives nil inside openFiles:, even though I’m invoking the app’s open handler from a script in SD.

Yet, when I set a breakpoint, e.g. in openURLs:, I see that it comes from the AE handling code:
Screen Shot 2022-09-28 at 19.24.49

However, based on your suggestion, I then found a solution. Instead of referring to the current scriptCommand, which apparently doesn’t exist for this standard suite command (odoc) (I have no sdef in case that’s relevant!), I refer to the current AE:

NSAppleEventManagerSuspensionID eventID = [NSAppleEventManager.sharedAppleEventManager suspendCurrentAppleEvent];
[NSAppleEventManager.sharedAppleEventManager resumeWithSuspensionID:eventID];

Basically, I suspend and immediately resume the event handling, and that makes the 5s delay go away. Weird, huh?

Weird indeed, as you say.