Repository navigation
fix: make update and init wait for deployments, and the reverse - #319
Merged
Merged
Conversation
update now holds ~/.configfile/lock, the lock modules deploy and undeploy take, for the whole sync (fetch, saving local changes, reset), and init while it clones or reuses the repository and saves the configuration, after its questions. A deployment never reads files a sync or a new configuration is replacing: each waits for the other, up to 10 seconds, and says it is waiting. Also fixes two weaknesses of the lock: a lock left empty by a process that died before writing its pid is taken over after 2 seconds, and a lock that cannot be removed no longer replaces the command's result. Closes #313
…once it waited - The lock is created with its owner's pid already in it: the pid is written to a draft file, then linked as the lock, which fails when a lock exists. It never exists empty, so the 2-second rule for empty locks is gone, and a lock without pid (from an older configfile) is waited for, never taken over. - A stale lock is taken over by one process at a time, holding lock.takeover: two processes could otherwise both remove it, one of them the lock the other had just taken. - A lock that cannot be read stops the command instead of being taken over: its owner cannot be checked. - A lock that cannot be removed at the end is reported, and a lock this process left behind is taken over at once by its next command. - init checks the folder and the configuration again once it holds the lock, as another configfile may have cloned or configured meanwhile. - Tests: concurrent takeover, an unreadable lock, a deployment waiting for an update, the waiting message of init and deploy, and a stricter check that the fetch runs under the lock.
…e process - A takeover that another process is doing is waited for like a lock, with the usual time limit and message, naming lock.takeover; it used to retry at once, forever, using a whole processor. - Calls of the same process take their turn, so they never hold the lock at once: the lock file cannot tell them apart. - The release reports a lock that was replaced or removed while held, not only one that cannot be removed. - A takeover guard left behind is moved aside and checked, and put back if a new one was moved by mistake. - Where hard links are not supported, the lock is created in two steps, as configfile 1.0 did. - Drafts left by processes that no longer exist are removed. - Tests: a takeover in progress, a guard left behind, the release checks, same-process calls, drafts, the fallback, init --force, and conditions instead of delays where a test waits for a command to wait.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #313.
configfile deploys the configuration files (dotfiles) of a git repository. It keeps its own copy of the repository, a mirror in
~/.configfile/dotfiles, and links your files to it.configfile updatesyncs that mirror with the remote: it fetches, saves any local changes as a patch, then resets the mirror to the remote withgit reset --hard.To keep two processes from changing the same files at once,
modules deployandmodules undeploytake a lock,~/.configfile/lock(src/lock.ts).updateandinitdid not, although they change what deployments read:updaterun during a deployment (two terminals, or a setup script running both) could reset the mirror while the deployment read it: a link could be made to a file the reset removes a moment later, or a local copy made from a half-updated file;initcould rewrite~/.configfilercwhile another command was reading it.updateinitmodules deploy,undeployscripts runA deployment and a sync now wait for each other, as two deployments already did: up to 10 seconds, then the command stops with an error naming the other process.
Other changes
Waiting for another configfile (process N) to finish…once each time it starts waiting.lock.takeover. Another process doing it is waited for like a lock, with the same time limit and message. A guard left by a process that died is moved aside and checked before being removed. One rare case remains: a guard left behind, found by several processes at the very same moment.Cannot take the lock …; a lock without pid (configfile 1.0 creates it empty for a moment) is waited for.Design
updateruns, nothing deploys.inittakes the lock after its questions, so someone thinking at the URL prompt never blocks other commands. Once it holds the lock, it checks the folder and the configuration again: another configfile may have cloned the folder (it is then reused) or saved a configuration (it then stops rather than replace it without asking). A git prompt for credentials during the clone, or duringupdate's fetch, does hold the lock.scripts runstays without the lock, as the issue proposes; the docs ask not to run scripts during anupdate.modules deploy --alltakes the lock module by module, so that scripts run by modules can run configfile themselves. Anupdatecan therefore still run between two modules of a deployment. Files are never deployed while the sync runs, but the module list and settings are read, without the lock, when the deployment starts: the modules after the sync are deployed with those settings. The safety page says so.Other Informations
Compatibility
updateandinitcan now fail withAnother configfile (process N) is changing files…when another command holds the lock for more than 10 seconds, or withCannot take the lock …when~/.configfilecannot be used. Before, they went ahead.--folderoutside it,initnow fails before cloning (its lock lives in~/.configfile), instead of cloning and then failing to save the configuration.modules deployandundeploycan now wait for anupdateorinit, and fail the same way when the fetch or clone takes more than 10 seconds.Cannot take the lock …), instead of after 10 seconds.initstopping when another configfile saved a configuration while it waited (unless--force).Review guide
src/lock.ts:acquireandcreateExclusive(the draft and the link),takeOverandremoveStale(thelock.takeoverguard),readOwner(an unreadable lock stops the command),release, the in-process queue inwithLock, andwithCommandLock, which every command uses to say it waits.src/commands/update.tsandsrc/commands/init.ts: where the lock is taken.src/commands/modules.ts: only switched towithCommandLock.concepts/safety.md,reference/modules.md,reference/scripts.md, and the changelog.Tests
pnpm checkpasses. New tests:updatewaits for another process holding the lock, says so once, then syncs;updateholds the lock during the fetch and the reset: a gitreference-transactionhook, run by both, records the lock's content each time;modules deploystarted during anupdatewaits for it, and deploys the synced files;updatefails before syncing when the lock cannot be taken, and the run is recorded in the history;initwaits for the lock after its question, saying so, and fails before cloning when the lock cannot be taken; once it waited, it reuses a folder cloned meanwhile, and does not replace a configuration saved meanwhile;modules deployandundeploysay they wait; a command whose lock cannot be removed warns and keeps its result;init --forcereplaces a configuration saved meanwhile;withLock:onWaitis called once with the other process;tests/lock-fallback.test.ts), the lock is still taken and waited for.reference-transactionhook are skipped on git older than 2.28.One existing
inittest, "explains how to recover when the configuration cannot be saved", made the whole home folder read-only; it now creates~/.configfilefirst, so it still tests the configuration failing after the clone.Manual test
From the root of a checkout of this branch (Node.js 24.11 or later, bash). It uses a throwaway home folder and takes about 25 seconds:
Steps 1 and 3 end with
Already up to date with origin/master; step 2 prints, for both commands (modules deployfirst printsDeploying zsh…), with the full path of the lock: