> On POSIX systems, in theory, we can just update the files in place. Except for dynamically loaded assets, this should work just fine. The OS treats file locking as advisory and open file descriptors are irrelevant, so even if the file is running you overwrite it in place and the user will get the freshest version on the next launch.
If you do this on macOS, and your code is codesigned, you are setting yourself for pain. Instead you should replace the file entirely so the file gets a new vnode.
Traditionally if you update the contents of a running binary in-place in Unix you're liable to get a SIGBUS. The way to do it is unlink the existing binary and create a new one with the same name. Which is eg what install(1) does.
If you want to do this really well I'd do it like A/B updates on Android work.
1. Have two directories for the app: A and B. One contains the "current" version and one contains (possibly) the "next" version.
2. Have the actual app you start be a separate launcher program that just picks which version to launch.
3. While the app is running, periodically check for updates. If an update is available download and unzip it to the A or B directory that isn't currently in use.
4. Next time the launcher program starts it can say "an updated version is already installed, would you like to use it" (or you can just do that automatically if you - or your user - decide that's the best option). If so it marks the appropriate A/B directory as "current" and launches that one.
Zero delay for users, works on all OSes, works with "always use the latest version" as well as "ask the user to update".
For Windows, there is an alternative approach: simply rename the running application to a different name (e.g. app.exe to app-old-timestamp.exe). This frees up the file name which can be overwritten to any other executable. The only problem I'm aware of is that this process may leave app.exe non-existent for a moment [1], but in return a need for keeping two executables vanishes.
[1] Technically speaking I believe this can be solved with transactional NTFS (deprecated but still available as of Windows 11, AFAIK).
Love that they prioritized both privacy (no sketchy background daemons) and security (launch-time checks) for legal apps—finally a team that gets how critical "no surprises" is for tools handling sensitive docs.
It should be the OS package manager's job on Windows, too. That wheel, and all the corner cases, doesn't need to be reinvented. Applications that update themselves louse-up least privilege user accounts, for example.
I’m surprised there’s no attempt to use an A/B or multi version approach. It’s only a partial solution, but the main binary could be a very minimal launcher that enumerates installed versions and decides which version to run. This results in no window at all in which the launcher doesn’t work, and it makes recovery from a bad update much, much easier.
Updating the launcher would still be tricky, but the launcher is tiny, so deleting old versions in a timely manner is not so important.
Why not just have a "A new version is available" thing pop up at the top of the window (that can be closed out)? A user is not necessarily connected to fast or cheap internet at all times. Automatically downloaded updates are a bane on infrastructure at remote field stations, for example.
Also, it any updating mechanism should be trivially overridable by configuration so that the system package manager can be used, if the application is installed by the system network manager. I hate it when applications manage their own updates... give me a yum repo instead.
> Windows generally treats running binaries as locked. Any attempt to overwrite a running binary should throw an exception. Again, the Zed source provides a reasonable solution.
While it's true that you can't overwrite a running binary on Windows, you can still rename it.
For example, the updater I wrote for AutoPTT downloads the new update to a temporary directory, renames all old files to ${file}.bak, then moves over the files from the temporary folder, and finally runs the new binary while exiting the old one.
14 comments
[ 5.2 ms ] story [ 42.9 ms ] threadIf you do this on macOS, and your code is codesigned, you are setting yourself for pain. Instead you should replace the file entirely so the file gets a new vnode.
1. Have two directories for the app: A and B. One contains the "current" version and one contains (possibly) the "next" version.
2. Have the actual app you start be a separate launcher program that just picks which version to launch.
3. While the app is running, periodically check for updates. If an update is available download and unzip it to the A or B directory that isn't currently in use.
4. Next time the launcher program starts it can say "an updated version is already installed, would you like to use it" (or you can just do that automatically if you - or your user - decide that's the best option). If so it marks the appropriate A/B directory as "current" and launches that one.
Zero delay for users, works on all OSes, works with "always use the latest version" as well as "ask the user to update".
I've never tried this tbf - just an idea.
[1] Technically speaking I believe this can be solved with transactional NTFS (deprecated but still available as of Windows 11, AFAIK).
And yet it's running on windows?!
Updating the launcher would still be tricky, but the launcher is tiny, so deleting old versions in a timely manner is not so important.
Also, it any updating mechanism should be trivially overridable by configuration so that the system package manager can be used, if the application is installed by the system network manager. I hate it when applications manage their own updates... give me a yum repo instead.
While it's true that you can't overwrite a running binary on Windows, you can still rename it.
For example, the updater I wrote for AutoPTT downloads the new update to a temporary directory, renames all old files to ${file}.bak, then moves over the files from the temporary folder, and finally runs the new binary while exiting the old one.