Crash in GetMacExecutableFSRef

I occasionally get a crash in this place, with 2016r1.1:

It happens when calling Application.ExecutableFile.Get

Any idea what that means? Has anyone else seen this?

Xojo can’t get the FSRef of the app?
Reported via Feedback?
Can be reproduced?

Or maybe app deleted while running?

While running the application in the debugger or while running the compiled application?

The latter.

A wild guess: are you calling it from a thread?

Happens in Thread 0 with an app downloaded from the App Store, not sandboxed.

I’ve seen the same crash happen occasionally when the non-MAS version of same app did copy itself to the Apps folder and then relaunched itself there - but that’s not the case here as the MAS version does not do that.

In any way, it’s bad coding style to use an Assert on testing a soft error (here: OSError). It should instead return nil so that my code has at least some way of dealing with it instead of just being killed.

Asserts are meant to be used to check “contracts”, e.g. if an function is documented to accept only certain values and it’s getting an invalid one, thereby telling the caller of the function that he must filter out the illegal values beforehand. And such things. Things that violate assumptions of your own code.

Not for checking things that you can easily escape from, like here, where returning nil would be no problem. That’s just lazy programming and harmful to us.

Interesting that there are two bug reports for this: 18540 and 23528.

One is from Thomas from 5 years ago.

I suspect it might have been done this way because it was genuinely thought to be impossible and returning Nil would be violating the documented API contract of Application.ExecutableFile. It obviously doesn’t hold true and something needs to be done differently.

Good catch. In my current case, the folder names were not renamed, AFAIK, though. App resided in /Applications. Might still be related to the App Store updating the app. Maybe the old app version was not quit in time, but App Store replaced it anyway? Hard to tell.

Unfortunately, I cannot easily reproduce it, so it’s hard to debug. Damn.

The gist is that this code, memory management elided, fails:

bool GetMacExecutableFSRef(FSRef *outRef)
{
	// CFBundle doesn't seem to track the bundle if it moves, so we'll just use
	// that FSRef we cached during the application launch.
	CFURLRef bundleURL = CFURLCreateFromFSRef(nullptr, &sAppRef);
	if (bundleURL) return false;

	CFBundleRef bundle = CFBundleCreate(nullptr, bundleURL);
	if (!bundle) return false;
	
	CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
	if (!executableURL) return false;
	
	return CFURLGetFSRef(executableURL, outRef);
}

Of course, at a higher level, the system frameworks themselves don’t support the application being moved while it’s running…

bundleURL is maybe invalid?
Why not CFBundleGetMainBundle?

What is bundleURL? I mean, is it an ID ref or a full file path ref?
Hah - Christian beat me to it :slight_smile:

Anyway, it all points to the possibility that the app is still running but has either been moved or deleted, I guess. Getting nil returned would allow my app to detect this and exit gracefully.

[quote=283834:@Thomas Tempelmann]What is bundleURL? I mean, is it an ID ref or a full file path ref?
[/quote]

Yeah, I realized it was lacking that bit of code after I posted and edited it. Apparently not quickly enough.

In my experience I found that NSCFBundle are cached, so once the bundle is loaded all information points to when it was loaded, even if you release the bundle and ask the OS for a new object.

I started using CFBundle for App Wrapper, but because it’s cached it meant you had to re-start App Wrapper every-time the application it was wrapping was changed.

Joe, in that code you posted, it’s still missing the part that sets sAppRef. How is that created?

Since GetMacExecutableFSRef() recreates the data every time, with sAppRef being a FSRef, this code would follow a moved app every time I call it, right? Contrary to using CFBundle, which caches the result and won’t update when the app gets moved?

I’ve now, by chance, identified one cause for this that’s easy to reproduce:

If a Mac has multiple users logged in, with two users both running the same app, then if the app gets updated (or manually replaced with an exact copy) by one user, switching to the other user will cause this error to appear because now the other app, trying to access files in its Resources folder, will find that its FSRef is no longer valid.

This could be fixed in Xojo, though: If, instead of using an FSRef, an absolute path were used to locate the app, then replacing the app’s files by an updater such as Sparkle would not cause this issue.

However, then it would cause issues if the user renamed or moved the running app, which should not cause problems with the current way using an FSRef (which remains intact as long as the app gets only moved around on the same volume).

I guess the best way would be to instead use an AliasRecord or the more modern bookmark - that will, if possible, prefer the path (which works if the app gets updated) and revert to the FSRef should the app get moved or renamed.

Joe, do you agree with this? Then I’ll open a ticket for this suggestion.

I would probably just follow Apple’s lead and just not support this situation (gracefully). Finder already warns users when they try to do this.

IIRC there is a plist entry to prevent multiple users from opening the same application at the same time, not saying you’d want to use it, but it’s there.

Could you not use [NSURL fileReferenceURL], create the reference when the application first starts and then in theory the ReferenceURL should follow the application, then create a FSREF from the reference URL?

Not if it’s running only on another user’s account, it seems.

Yeah, not the solution I seek, nor the one anyone else will even think of beforehand until these unexplainable errors occasionally pop up.

No, an updater will usually first rename the running app, then install the new one. That means the ref would follow the old, deleted app, which is exactly what I’m seeing to happen.

Anyway, even the bookmark idea is not foolproof - if the update removes some files that the old version references, the app would still have failures after the update. The only sensible thing is to make sure the app can DETECT that it’s gotten replaced, and then gracefully quit or restart itself, and for that, we simply need that code not to Assert but to return nil.

Joe, is that in the works, at least? It would be the safest and probably also easiest fix.