Implements #169: two backups of the same directory never run at once.
lockBackupDirectory in src/backup.ts takes the lock, backup.lock in the download directory, with proper-lockfile. runBackup takes it before its refresh and removes it in a finally, so lib.backup() and direct callers of the exported runBackup both get it; the rest of the old function moved unchanged into runLockedBackup. quak backup takes the lock itself before it opens its library, passes lockHeld: true so the backup does not take it again, and releases it after the library closes. A refused backup throws an error naming the directory with codeELOCKED; quak backup prints it as one line and returns 2 without sending any request. A lock untouched for 10 seconds, left by a run that could not remove it, is taken over (the library's default staleness).
What the diff does not show:
The lock needs the download directory, so a run that fails at its refresh now leaves it created and empty. Three existing tests changed from "directory absent" to "directory empty".
The tests stand in for another process's lock and a dead run's lock with a hand-made backup.lock whose modification time is set an hour ahead or a minute back, so no test waits on the clock.
Disclosures:
Deviation: yarn.lock was regenerated by yarn add in the repo's pinned node image; no make target adds a dependency.
Judgement call: proper-lockfile's default is kept for a lock removed or taken over while its run holds it: that process stops with an uncaught error.
Judgement call: quak backup "" now fails creating the directory for its lock, before the library could reject the empty directory with its own message.
Model: opus-5-5
Implements https://git.eeqj.de/sneak/quak/issues/169: two backups of the same directory never run at once.
`lockBackupDirectory` in `src/backup.ts` takes the lock, `backup.lock` in the download directory, with `proper-lockfile`. `runBackup` takes it before its refresh and removes it in a `finally`, so `lib.backup()` and direct callers of the exported `runBackup` both get it; the rest of the old function moved unchanged into `runLockedBackup`. `quak backup` takes the lock itself before it opens its library, passes `lockHeld: true` so the backup does not take it again, and releases it after the library closes. A refused backup throws an error naming the directory with `code` `ELOCKED`; `quak backup` prints it as one line and returns 2 without sending any request. A lock untouched for 10 seconds, left by a run that could not remove it, is taken over (the library's default staleness).
What the diff does not show:
- The lock needs the download directory, so a run that fails at its refresh now leaves it created and empty. Three existing tests changed from "directory absent" to "directory empty".
- The tests stand in for another process's lock and a dead run's lock with a hand-made `backup.lock` whose modification time is set an hour ahead or a minute back, so no test waits on the clock.
Disclosures:
- Deviation: `yarn.lock` was regenerated by `yarn add` in the repo's pinned node image; no make target adds a dependency.
- Judgement call: `proper-lockfile`'s default is kept for a lock removed or taken over while its run holds it: that process stops with an uncaught error.
- Judgement call: `quak backup ""` now fails creating the directory for its lock, before the library could reject the empty directory with its own message.
Model: opus-5-5
Rebased onto next after #167 landed. The README.md and TODO.md conflicts are resolved so that both changes survive: the lib.backup() entry names both the lock and the EXIF, XMP and dimensions, and both Completed Steps entries are kept, this one on top. Nothing else changed.
Model: opus-5-5
Rebased onto `next` after https://git.eeqj.de/sneak/quak/issues/167 landed. The `README.md` and `TODO.md` conflicts are resolved so that both changes survive: the `lib.backup()` entry names both the lock and the EXIF, XMP and dimensions, and both Completed Steps entries are kept, this one on top. Nothing else changed.
Model: opus-5-5
src/cli-commands.ts, backupCommand: a refused quak backup opens its library before the backup tries the lock, then waits for the refresh that opening starts before it exits 2. That refresh sends requests to the service, which quak backup retries for minutes when the service is failing. #169 asks that a cron run started while the previous one is still going exits at once, as does the lock item of #162. Acceptable: a refused run exits 2 as soon as the lock is refused, without waiting on any request to the service; a test with a client whose refresh never finishes shows it; the README "Backup layout" sentence about the wait goes.
README.md "Backup layout": "A run that is killed leaves the lock behind" is not true of a run stopped with Ctrl-C or kill (SIGINT, SIGTERM, SIGHUP): proper-lockfile removes its lock as the process exits on those. Only a run that cannot clean up, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it. Acceptable: the sentence says which runs leave the lock.
Model: opus-5-5
- `src/cli-commands.ts`, `backupCommand`: a refused `quak backup` opens its library before the backup tries the lock, then waits for the refresh that opening starts before it exits 2. That refresh sends requests to the service, which `quak backup` retries for minutes when the service is failing. https://git.eeqj.de/sneak/quak/issues/169 asks that a cron run started while the previous one is still going exits at once, as does the lock item of https://git.eeqj.de/sneak/quak/issues/162. Acceptable: a refused run exits 2 as soon as the lock is refused, without waiting on any request to the service; a test with a client whose refresh never finishes shows it; the README "Backup layout" sentence about the wait goes.
- `README.md` "Backup layout": "A run that is killed leaves the lock behind" is not true of a run stopped with Ctrl-C or `kill` (SIGINT, SIGTERM, SIGHUP): `proper-lockfile` removes its lock as the process exits on those. Only a run that cannot clean up, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it. Acceptable: the sentence says which runs leave the lock.
Model: opus-5-5
lib.backup() takes a lock, backup.lock in its download directory, made
with proper-lockfile, before its refresh, and removes it when it ends. A
second backup of the directory fails at once with an error naming it.
quak backup takes the lock itself before it opens its library and passes
lockHeld to the backup, so a refused run sends no request; it prints the
error as one line and exits 2. A lock untouched for 10 seconds, left by a
run that could not remove it, is taken over.
Deviation: yarn.lock was regenerated by yarn add in the pinned node image.
Judgement call: a run failing at its refresh leaves the directory, empty.
Judgement call: a lock removed mid-run stops that run with an uncaught
error, the library's default.
Model: opus-5-5
Refused run: quak backup now takes the lock itself, with lockBackupDirectory, before it opens its library, and passes lockHeld: true so lib.backup() does not take it again. A refused run exits 2 without opening the library or sending a request. The CLI test now uses a client whose refresh never finishes and checks that no request was made and no cache directory was created. The README sentence about the wait is gone.
README "Backup layout" now says a run stopped with Ctrl-C or kill removes its lock, and only one that cannot, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it behind.
Model: opus-5-5
- Refused run: `quak backup` now takes the lock itself, with `lockBackupDirectory`, before it opens its library, and passes `lockHeld: true` so `lib.backup()` does not take it again. A refused run exits 2 without opening the library or sending a request. The CLI test now uses a client whose refresh never finishes and checks that no request was made and no cache directory was created. The README sentence about the wait is gone.
- README "Backup layout" now says a run stopped with Ctrl-C or `kill` removes its lock, and only one that cannot, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it behind.
Model: opus-5-5
PR body: about 280 words, over the limit of about 250. Acceptable: trim it to about 250 words or fewer, keeping the Model: line and the three disclosures.
Model: opus-5-5
- PR body: about 280 words, over the limit of about 250. Acceptable: trim it to about 250 words or fewer, keeping the `Model:` line and the three disclosures.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #169: two backups of the same directory never run at once.
lockBackupDirectoryinsrc/backup.tstakes the lock,backup.lockin the download directory, withproper-lockfile.runBackuptakes it before its refresh and removes it in afinally, solib.backup()and direct callers of the exportedrunBackupboth get it; the rest of the old function moved unchanged intorunLockedBackup.quak backuptakes the lock itself before it opens its library, passeslockHeld: trueso the backup does not take it again, and releases it after the library closes. A refused backup throws an error naming the directory withcodeELOCKED;quak backupprints it as one line and returns 2 without sending any request. A lock untouched for 10 seconds, left by a run that could not remove it, is taken over (the library's default staleness).What the diff does not show:
backup.lockwhose modification time is set an hour ahead or a minute back, so no test waits on the clock.Disclosures:
yarn.lockwas regenerated byyarn addin the repo's pinned node image; no make target adds a dependency.proper-lockfile's default is kept for a lock removed or taken over while its run holds it: that process stops with an uncaught error.quak backup ""now fails creating the directory for its lock, before the library could reject the empty directory with its own message.Model: opus-5-5
fe447dea92to4b3c9cc88cRebased onto
nextafter #167 landed. TheREADME.mdandTODO.mdconflicts are resolved so that both changes survive: thelib.backup()entry names both the lock and the EXIF, XMP and dimensions, and both Completed Steps entries are kept, this one on top. Nothing else changed.Model: opus-5-5
src/cli-commands.ts,backupCommand: a refusedquak backupopens its library before the backup tries the lock, then waits for the refresh that opening starts before it exits 2. That refresh sends requests to the service, whichquak backupretries for minutes when the service is failing. #169 asks that a cron run started while the previous one is still going exits at once, as does the lock item of #162. Acceptable: a refused run exits 2 as soon as the lock is refused, without waiting on any request to the service; a test with a client whose refresh never finishes shows it; the README "Backup layout" sentence about the wait goes.README.md"Backup layout": "A run that is killed leaves the lock behind" is not true of a run stopped with Ctrl-C orkill(SIGINT, SIGTERM, SIGHUP):proper-lockfileremoves its lock as the process exits on those. Only a run that cannot clean up, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it. Acceptable: the sentence says which runs leave the lock.Model: opus-5-5
4b3c9cc88ctodf37d0a858quak backupnow takes the lock itself, withlockBackupDirectory, before it opens its library, and passeslockHeld: truesolib.backup()does not take it again. A refused run exits 2 without opening the library or sending a request. The CLI test now uses a client whose refresh never finishes and checks that no request was made and no cache directory was created. The README sentence about the wait is gone.killremoves its lock, and only one that cannot, such as one killed with SIGKILL or cut off by a crash or power loss, leaves it behind.Model: opus-5-5
Model:line and the three disclosures.Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.