While working on a project for a client (updating an OS X app), we ran into a problem that we first thought was a bug in the app itself. Entire portions of the interface were completely non-functional but only on the client’s machine. I inherited large portions of technical debt with the project, so I figured it was just something in there.
Bug troubleshooting seems like it’s 95% luck at times, especially when the conditions for reproducing it seem totally random. This was one of those cases — everything I started on ended up as a dead end. After a couple days, it was only out of pure dumb luck that I stumbled on the recipe to reproduce the problem: uninstall the previous version of the app. A full uninstall.
Sandboxed apps store their data at ~/Library/Containers/\
The Underlying Behavior
At least as far back as OS X 10.9 (Mavericks), OS X has been caching preferences using a daemon process called cfprefsd
. This process mediates access to the preferences system and holds on to an open file descriptor for each property list accessed, presumably to speed up access.
cfprefsd
does not seem to monitor the property list files for any external changes though — it assumes it will be the only process accessing them. Further, running the Terminal command defaults delete com.example.myapp
doesn’t actually delete the preferences file. Instead, it sets the contents to an empty property list and leaves the file descriptor open.
The Result
By deleting the entire group container for the app, this also deletes the file system entry for the preferences file. Rather than re-create the file, cfprefsd
holds on to a stale file descriptor. All calls to read or set values via NSUserDefaults
then turn into no-ops, and the only way to force restoration of the correct behavior is to restart cfprefsd
: reboot the system or killall cfprefsd
.
Radar #24217396.