feat(lib): use structured lock file with metadata

This allows for us to know whether a lock file is too old and hence
invalid, and perform a sanity check to ensure that the resource it is
meant to be protecting is the resource we are trying to access.
This commit is contained in:
Erica Marigold 2025-01-21 22:39:07 +05:30
parent 57605e87b8
commit aef778883e
Signed by: DevComp
GPG key ID: 429EF1C337871656

View file

@ -178,37 +178,47 @@ function installTool(tool: ToolId, installPath: pathfs.Path): number
pathfs.writeDir(toolDir) pathfs.writeDir(toolDir)
end end
type InstallLock = {
expiration: number,
resource: string,
}
-- Attempt to read an existing lock in EAFP fashion -- Attempt to read an existing lock in EAFP fashion
local installLockFile = toolDir:join("LOCK") local installLockFile = toolDir:join("LOCK")
local isLocked, existingInstallPath = pcall(pathfs.readFile, installLockFile) local isLocked, lockFileContents = pcall(pathfs.readFile, installLockFile)
if isLocked then if isLocked then
-- If the lock was held and we know the same tool that we are trying to install -- If the lock was held and we know the same tool that we are trying to install
-- is also being installed elsewhere, we wait for that installation attempt to -- is also being installed elsewhere, we wait for that installation attempt to
-- finish, i.e., the lock file is removed, and then run the freshly installed tool -- finish, i.e., the lock file is removed, and then run the freshly installed tool
if installPath:toString() == existingInstallPath then -- Disable the progress bar since we're not actually an installation process
-- Disable the progress bar since we're not actually an installation process _G.interactive = false
_G.interactive = false bar:stop()
bar:stop()
warn("Waiting for existing installation process for tool to exit") local lockFile: InstallLock = serde.decode("json", lockFileContents)
assert(lockFile.resource == installPath:toString(), "Mistmatch between lock and expected resource")
local lockWatchStart = os.clock() warn("Waiting for existing installation process for tool to exit")
while pathfs.isFile(installLockFile) do while pathfs.isFile(installLockFile) do
if os.clock() - lockWatchStart > 30_000 then if os.time() > lockFile.expiration then
-- If more than 30s has passed since we started waiting for the lock -- If more than 30s has passed since we started waiting for the lock
-- to be released, we assume something went wrong and error -- to be released, we assume something went wrong and error; this should
error("Installation lock was held for too long (>30s)") -- also remove the lock for subsequent runs
end error("Installation lock was held for too long (>60s)")
task.wait(1)
end end
return runTool(installPath) task.wait(1)
end end
return runTool(installPath)
end end
-- Write a lock file to prevent concurrent installation attempts -- Write a lock file to prevent concurrent installation attempts
pathfs.writeFile(installLockFile, installPath:toString()) local lockFile: InstallLock = {
resource = installPath:toString(),
expiration = os.time() + 60_000, -- lock expires in 60s from time of issue
}
pathfs.writeFile(installLockFile, serde.encode("json", lockFile))
local toolAlias = toolAliasOrDefault(tool) local toolAlias = toolAliasOrDefault(tool)
local client = Github.new( local client = Github.new(