Repository navigation
Replies: 1 comment
|
Your asymmetry is the right way to describe it: one of the two waits is bounded and the other is not.
// packages/boot/plugin-manager/src/index.ts:852
return hmr === undefined ? apply() : hmr.runExclusive(apply)
// :903 / :925
return withFileLock(join(this.profile.dir, 'package.json'), async () => { ... }, { waitMs: this.lockWaitMs })
const task = this.operations.then(async () => { ... return this.executing.run(true, operation) })
this.operations = task.catch(() => {})
return task
Two things worth separating in the report:
The smallest fix that restores the property you expected: wrap the queued operation in a timeout inside |
Uh oh!
There was an error while loading. Please reload this page.
Summary
PluginManager.change()holds apackage.jsonfile lock for the whole mutation andwaits up to
lockWaitMs(default 120 000 ms) for it; the HMRrunExclusivequeue that the same call enters has no timeout at all. When a mutation hangs once
(e.g. the plugin market's pre-fix
add name@versionpath, below), the profile staysunusable: every later install/remove waits the full 120 s, and the market's own
mutation guard answers
409 another install is already running— the UI just spins.Code
dsh/node_modules/@deepseek-ai/dsh-plugin-manager/lib/index.js:2373-2401—change()wraps the whole operation inwithFileLock(join(this.profile.dir, "package.json"), …, { waitMs: this.lockWaitMs }):1991— update path rejection:if (this.ownerContext.get("hmr") !== void 0 && known !== void 0 && Object.hasOwn(before, known)) throw new ManagementFailure("already-installed");:1373-1379—specPackageName()returns the bare name for aregistryspec, soadd foo@2.0.0for an already-installedfoois refused before pnpm runs(pre-fix market 1.66.28 sent exactly that for updates:
lib/routes.js:4322-4324)dsh/node_modules/@deepseek-ai/dsh-hmr/lib/index.js:748-756—runExclusive()chains work ontothis.operationswith no deadline andno cancellation; one task that never settles blocks every later request.
dsh/node_modules/@deepseek-ai/dsh-atomic-write/lib/index.js:189-218—withFileLock()creates<file>.lockwith the holder PID, retries, andthrows
atomic-write: timed out waiting for the writer lock at …afterwaitMs.The holder PID is only checked for liveness, so a live-but-stuck host is retried for
the full two minutes on every call.
Reproduction
update attempt is enough) and leave the session open.
with
{"code":"already-installed"}and then attempts a rollback.~/.dsh/profiles/desktop/package.json.lockcontaining the live host PID,package.json/pnpm-lock.yamlrewritten, and nopnpmchild process alive.update-rollback … restoration of the previous build could not be verified.Market log excerpt (
~/.dsh/profiles/desktop/.dsh-market/log.ndjson):{"event":"update","detail":"dshmarket -> dshmarket@1.66.45 exit=1 err={\"code\":\"already-installed\"}"} {"event":"update-rollback","detail":"… failed update command and restoration of the previous build could not be verified — {\"code\":\"already-installed\"}"} {"event":"uninstall","detail":"… exit=0 live-removed=false"}Expected
since the holder is the host itself and cannot make progress.
Suggested fixes
runExclusiveand the lock wait, and surface a distinctprofile-busyerror.(lease/heartbeat) and break it.
installOrReinstall(),dshmarket/lib/official-desktop.js:150-178), soalready-installedcannot be produced for an upgrade.Local workaround used
Deleted the stale
package.json.lock, upgraded the market to 1.66.45 with pnpm, andadded the new market version to
pnpm-workspace.yamlminimumReleaseAgeExclude(pnpm 11's 24 h release wait applies to the profile's lockfile, so upgrading to a
freshly published version otherwise makes every later pnpm operation fail with
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION).All reactions