Skip to content

Commit cd25034

Browse files
anandgupta42claude
andcommitted
fix(drivers): handle UNC paths, and give the install lock a per-holder budget
Three review-round gaps, two of them Windows shapes that a macOS suite cannot notice on its own. **`installLockPath` broke on a UNC share root.** It special-cased the POSIX and drive-letter roots but not `\\server\share`, which stripped to `\\server\share.lock` — naming a *different network share* rather than anything inside the directory being locked. Two processes installing into a share would then take different locks, which is the concurrent npm mutation the lock exists to prevent. Root detection now covers all three shapes and the lock always lands inside the root. **The harvesting regex accepted no UNC path.** A Windows error quoting `\\server\share\node_modules\duckdb\package.json` yielded no roots at all, so a driver on a share stayed unfindable even though the error named its exact location. The pattern is now a named export, so the claim is testable from any platform instead of resting on inspection. **The lock wait outlasted only one peer.** Every process counts its deadline from its own start, so a single budget covers a single holder; with three or more contenders the last one's deadline expired part-way through somebody else's install and it fell through to an unlocked `performInstall`. The budget is now per holder — seeing the lock change hands is proof the queue is moving rather than wedged — with a bounded number of extensions so a machine that keeps feeding in contenders cannot block a caller indefinitely. Each test fails with its own fix reverted and passes with it: 1 of 10 for each Windows shape, 1 of 14 for the multi-peer wait. The multi-peer test spawns real processes, holds 400ms against a 600ms budget, and asserts none of the three ran unlocked. 12/12 repeat runs green. Also hardens the chdir arm per review. It asked for `duckdb`, which the repo's own `packages/drivers/node_modules` satisfies through the execPath and module-location roots no matter what the resolver does — it would have passed while proving nothing. It now uses specifiers that exist nowhere but the tree each test builds and asserts on an export marker, so a pass establishes which root satisfied the load. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ
1 parent 8e4cdc2 commit cd25034

4 files changed

Lines changed: 228 additions & 25 deletions

File tree

‎packages/drivers/src/resolve.ts‎

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,29 @@ function isWorkspaceRoot(root: string, scope: { cwd: string | undefined; ancesto
299299
* resolved credentials; mining a path out of an error message must not become a
300300
* way around that invariant.
301301
*/
302+
/**
303+
* Absolute paths a runtime quoted inside an error message.
304+
*
305+
* Three absolute shapes, and the third is the one that is easy to leave out:
306+
* POSIX `/…`, a drive letter `C:\…`, and a UNC share `\\server\share\…`. A
307+
* Windows error naming a driver on a share yields no roots at all without it,
308+
* so a driver stays unfindable even though the error named its exact location.
309+
*/
310+
export function quotedAbsolutePaths(message: string): string[] {
311+
const found: string[] = []
312+
const pattern = /['"`]((?:\/|[A-Za-z]:[\\/]|\\\\[^\\/'"`\n]+[\\/])[^'"`\n]+)['"`]/g
313+
for (const match of message.matchAll(pattern)) {
314+
const named = match[1]
315+
if (named) found.push(named)
316+
}
317+
return found
318+
}
319+
302320
export function searchRootsFromError(error: unknown): string[] {
303321
const message = error instanceof Error ? error.message : String(error)
304322
const scope = workspaceScope()
305323
const roots: string[] = []
306-
// Absolute paths the runtime quoted, POSIX or Windows.
307-
for (const match of message.matchAll(/['"`]((?:\/|[A-Za-z]:[\\/])[^'"`\n]+)['"`]/g)) {
308-
const named = match[1]
309-
if (!named) continue
324+
for (const named of quotedAbsolutePaths(message)) {
310325
for (const candidate of [named, repairCwdPrefixedPath(named)]) {
311326
if (!candidate) continue
312327
// Walk back to every enclosing node_modules directory, innermost first.
@@ -1016,13 +1031,32 @@ const installsInFlight = new Map<string, Promise<InstallResult>>()
10161031
*/
10171032
export function installLockPath(dir: string): string {
10181033
const trimmed = dir.replace(/[\\/]+$/, "")
1034+
const separator = dir.includes("\\") ? "\\" : "/"
10191035
// Trailing separators are stripped so `<dir>/` and `<dir>` agree on one lock.
1020-
// A filesystem root is the exception: stripping there destroys the root — "/"
1021-
// would become the *relative* ".lock", and "C:\" the drive-relative "C:.lock"
1022-
// — so two processes started from different working directories would take
1023-
// different locks while installing into the same place.
1024-
const base = trimmed === "" || /^[A-Za-z]:$/.test(trimmed) ? dir : trimmed
1025-
return `${base}.lock`
1036+
// A filesystem root is the exception: stripping there destroys the root, and
1037+
// the result names something *outside* the directory being locked, so two
1038+
// processes installing into the same place take different locks. Roots keep
1039+
// their separator and the lock goes inside them.
1040+
//
1041+
// / -> /.lock not the relative .lock
1042+
// C:\ -> C:\.lock not drive-relative C:.lock
1043+
// \\server\share\ -> \\server\share\.lock not a *different share*
1044+
if (isFilesystemRoot(trimmed)) return `${trimmed}${separator}.lock`
1045+
return `${trimmed}.lock`
1046+
}
1047+
1048+
/**
1049+
* True when `candidate` — trailing separators already stripped — names a
1050+
* filesystem root rather than a directory inside one.
1051+
*
1052+
* The UNC case is the one that is easy to miss: a share root is a root in
1053+
* exactly the way a drive letter is, and appending `.lock` to it names an
1054+
* unrelated share rather than anything under the directory being locked.
1055+
*/
1056+
function isFilesystemRoot(candidate: string): boolean {
1057+
if (candidate === "") return true // POSIX "/" strips to ""
1058+
if (/^[A-Za-z]:$/.test(candidate)) return true // "C:\" strips to "C:"
1059+
return /^\\\\[^\\/]+\\[^\\/]+$/.test(candidate) // "\\server\share"
10261060
}
10271061

10281062
/**
@@ -1204,6 +1238,24 @@ function releaseInstallLock(lockDir: string, token: string, ino: number | undefi
12041238
}
12051239
}
12061240

1241+
/**
1242+
* A value that changes when the lock changes hands.
1243+
*
1244+
* The owner's token when it published one; otherwise the lock directory's own
1245+
* identity, which a fresh `mkdir` changes even when the owner file is missing
1246+
* or unreadable. `undefined` means we could not tell, and the caller then
1247+
* treats the holder as unchanged rather than inventing progress.
1248+
*/
1249+
function lockIdentity(lockDir: string, holder: LockHolder | undefined): string | undefined {
1250+
if (holder?.token) return holder.token
1251+
try {
1252+
const stat = fs.statSync(lockDir)
1253+
return `${stat.ino}:${stat.mtimeMs}`
1254+
} catch {
1255+
return undefined
1256+
}
1257+
}
1258+
12071259
/**
12081260
* Run `fn` while holding an exclusive lock on `dir`, across processes.
12091261
*
@@ -1222,7 +1274,18 @@ export async function withInstallLock<T>(
12221274
const staleAfterMs = options.staleAfterMs ?? 300_000
12231275
const hardStaleAfterMs = options.hardStaleAfterMs ?? Math.max(staleAfterMs, 3_600_000)
12241276
const pollMs = options.pollMs ?? 100
1225-
const deadline = Date.now() + timeoutMs
1277+
let deadline = Date.now() + timeoutMs
1278+
// The budget is per *holder*, not per wait. Every process counts its deadline
1279+
// from its own start, so a single budget only ever outlasts one peer: with
1280+
// three or more contenders the last one's deadline expires part-way through
1281+
// somebody else's install and it falls through to an unlocked performInstall
1282+
// — the concurrent npm mutation this lock exists to prevent. Seeing the lock
1283+
// change hands is proof the queue is moving rather than wedged, so each new
1284+
// holder gets its own budget. Extensions are capped so a machine that keeps
1285+
// feeding in contenders cannot block a caller indefinitely.
1286+
const maxHandovers = 32
1287+
let handovers = 0
1288+
let lastHolder: string | undefined
12261289
const token = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
12271290
let acquired = false
12281291
let ino: number | undefined
@@ -1256,6 +1319,14 @@ export async function withInstallLock<T>(
12561319
const code = e && typeof e === "object" && "code" in e ? (e as { code?: unknown }).code : undefined
12571320
if (code !== "EEXIST") break
12581321
const holder = readLockHolder(lockDir)
1322+
const identity = lockIdentity(lockDir, holder)
1323+
if (identity !== undefined && identity !== lastHolder) {
1324+
if (lastHolder !== undefined && handovers < maxHandovers) {
1325+
handovers++
1326+
deadline = Date.now() + timeoutMs
1327+
}
1328+
lastHolder = identity
1329+
}
12591330
if (isStaleLock(lockDir, holder, staleAfterMs, hardStaleAfterMs)) {
12601331
// A claim can fail persistently — a lock owned by another user, or a
12611332
// container that permits inspection but not rename. Retrying such a

‎packages/drivers/test/install-lock.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,43 @@ process.exit(0)
133133
expect(sawAcquired).toBe(true)
134134
})
135135

136+
test("waits behind more than one peer without falling through unlocked", async () => {
137+
// Every process counts its deadline from its own start, so a single budget
138+
// only ever outlasts ONE holder. With three contenders the last one's
139+
// deadline expires part-way through somebody else's install and it runs
140+
// performInstall unlocked — the concurrent npm mutation the lock exists to
141+
// prevent. The hold below is deliberately longer than the timeout so the
142+
// test fails unless the wait is extended each time the lock changes hands.
143+
const target = path.join(dir, "drivers")
144+
fs.mkdirSync(target, { recursive: true })
145+
const log = path.join(dir, "peers.txt")
146+
147+
const child = path.join(dir, "peer.ts")
148+
fs.writeFileSync(
149+
child,
150+
`import fs from "node:fs"
151+
import { withInstallLock } from ${JSON.stringify(resolveModule)}
152+
const [target, log, id] = process.argv.slice(2)
153+
await withInstallLock(target, async (acquired) => {
154+
fs.appendFileSync(log, \`\${acquired ? "locked" : "UNLOCKED"} \${id}\\n\`)
155+
if (acquired) await new Promise((r) => setTimeout(r, 400))
156+
}, { timeoutMs: 600, pollMs: 20 })
157+
process.exit(0)
158+
`,
159+
)
160+
161+
const kids = Array.from({ length: 3 }, (_, i) =>
162+
Bun.spawn(["bun", child, target, log, String(i)], { stdout: "ignore", stderr: "ignore" }),
163+
)
164+
expect(await Promise.all(kids.map((k) => k.exited))).toEqual([0, 0, 0])
165+
166+
const events = fs.readFileSync(log, "utf8").trim().split("\n").filter(Boolean)
167+
expect(events.length).toBe(3)
168+
// Three holds of 400ms against a 600ms budget: the third can only succeed
169+
// if watching the lock change hands renewed its wait.
170+
expect(events.filter((e) => e.startsWith("UNLOCKED"))).toEqual([])
171+
}, 60_000)
172+
136173
test("reports the section ran unlocked when the lock cannot be taken in time", async () => {
137174
const target = path.join(dir, "drivers")
138175
fs.mkdirSync(target, { recursive: true })

‎packages/drivers/test/resolve-chdir.test.ts‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@ import fs from "node:fs"
33
import os from "node:os"
44
import path from "node:path"
55

6+
import { createRequire } from "node:module"
7+
import { pathToFileURL } from "node:url"
8+
69
import { driverSearchRoots, resolveOptionalPackage } from "../src/resolve"
710

11+
// Specifiers that exist nowhere but the tree each test builds. Asking for a
12+
// real driver name would let the repo's own `packages/drivers/node_modules`
13+
// satisfy the lookup through the execPath and module-location roots — which no
14+
// environment isolation can suppress — so the test would pass while proving
15+
// nothing about which root actually won.
16+
const FIXTURE = "altimate-chdir-fixture"
17+
const CWD_ONLY = "altimate-cwd-only-fixture"
18+
const MARKER = "resolved-from-the-fixture-tree"
19+
820
// Six pilots were spent on a driver-load failure that only appeared under
921
// `--dir`, which calls `process.chdir()` (cli/cmd/run.ts) before any driver is
1022
// loaded. Every local verification — and the rig's own pre-flight probe — ran
@@ -34,7 +46,7 @@ beforeEach(() => {
3446
pkgRoot = path.join(root, "lib", "node_modules", "altimate-code")
3547
nodeModules = path.join(pkgRoot, "node_modules")
3648
fs.mkdirSync(nodeModules, { recursive: true })
37-
writePackage(nodeModules, "duckdb", "index.js", "module.exports = { Database: function () {} }\n")
49+
writePackage(nodeModules, FIXTURE, "index.js", `module.exports = { marker: ${JSON.stringify(MARKER)} }\n`)
3850
elsewhere = path.join(root, "unrelated-run-dir")
3951
fs.mkdirSync(elsewhere, { recursive: true })
4052
})
@@ -46,11 +58,11 @@ afterEach(() => {
4658

4759
describe("resolution does not depend on the working directory", () => {
4860
test("resolves the same package before and after a chdir", () => {
49-
const before = resolveOptionalPackage("duckdb", [nodeModules])
61+
const before = resolveOptionalPackage(FIXTURE, [nodeModules])
5062
expect(before).toBeDefined()
5163

5264
process.chdir(elsewhere)
53-
const after = resolveOptionalPackage("duckdb", [nodeModules])
65+
const after = resolveOptionalPackage(FIXTURE, [nodeModules])
5466
expect(after).toBe(before)
5567
})
5668

@@ -59,34 +71,36 @@ describe("resolution does not depend on the working directory", () => {
5971
// about it can contribute to resolution. This is the rig's shape: the run
6072
// directory and the install tree are unrelated.
6173
process.chdir(elsewhere)
62-
const resolved = resolveOptionalPackage("duckdb", [nodeModules])
74+
const resolved = resolveOptionalPackage(FIXTURE, [nodeModules])
6375
expect(resolved).toBeDefined()
6476
expect(resolved!.startsWith(nodeModules)).toBe(true)
65-
expect(fs.existsSync(resolved!)).toBe(true)
77+
// Load it and read the marker, so the test reports which root satisfied the
78+
// lookup rather than merely that something was found.
79+
const loaded = createRequire(pathToFileURL(resolved!).href)(resolved!)
80+
expect(loaded.marker).toBe(MARKER)
6681
})
6782

6883
test("does not resolve out of the working directory's own node_modules", () => {
6984
// A package present only under cwd must stay invisible: project trees are
7085
// workspace-controlled executable content and are deliberately not searched.
7186
const cwdModules = path.join(elsewhere, "node_modules")
7287
fs.mkdirSync(cwdModules, { recursive: true })
73-
writePackage(cwdModules, "pg", "index.js", "module.exports = { Client: function () {} }\n")
88+
writePackage(cwdModules, CWD_ONLY, "index.js", `module.exports = { marker: ${JSON.stringify(MARKER)} }\n`)
7489

7590
process.chdir(elsewhere)
76-
// Assert on provenance, not absence: this repo legitimately has `pg` under
77-
// its own tree, which driverSearchRoots finds. What must never happen is a
78-
// resolution out of the working directory.
79-
const resolved = resolveOptionalPackage("pg", driverSearchRoots())
80-
if (resolved !== undefined) expect(resolved.startsWith(elsewhere)).toBe(false)
91+
// The specifier exists nowhere else on the machine, so this is absence with
92+
// a known cause: anything but undefined means the lookup reached into the
93+
// working directory.
94+
expect(resolveOptionalPackage(CWD_ONLY, driverSearchRoots())).toBeUndefined()
8195
})
8296

8397
test("a chdir between resolve and re-resolve does not change the answer", () => {
8498
process.chdir(elsewhere)
85-
const first = resolveOptionalPackage("duckdb", [nodeModules])
99+
const first = resolveOptionalPackage(FIXTURE, [nodeModules])
86100
process.chdir(originalCwd)
87-
const second = resolveOptionalPackage("duckdb", [nodeModules])
101+
const second = resolveOptionalPackage(FIXTURE, [nodeModules])
88102
process.chdir(root)
89-
const third = resolveOptionalPackage("duckdb", [nodeModules])
103+
const third = resolveOptionalPackage(FIXTURE, [nodeModules])
90104
expect(second).toBe(first)
91105
expect(third).toBe(first)
92106
})
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, expect, test } from "bun:test"
2+
3+
import { enclosingNodeModulesRoots, installLockPath, quotedAbsolutePaths } from "../src/resolve"
4+
5+
// Windows path shapes, unit-testable from any platform because none of these
6+
// touch the filesystem. UNC is the shape that keeps getting left out: a share
7+
// root behaves like a drive root, and code that only special-cases `C:` gets it
8+
// wrong in a way that is invisible until someone runs from a network share.
9+
10+
describe("installLockPath keeps a root intact", () => {
11+
test("puts the lock inside a POSIX root", () => {
12+
// Stripping the separator would leave "", making the lock the *relative*
13+
// ".lock" — a different file for every working directory.
14+
expect(installLockPath("/")).toBe("/.lock")
15+
})
16+
17+
test("puts the lock inside a drive root", () => {
18+
// "C:.lock" is drive-relative, not the root of C:.
19+
expect(installLockPath("C:\\")).toBe("C:\\.lock")
20+
})
21+
22+
test("puts the lock inside a UNC share root", () => {
23+
// The bug this pins: "\\\\server\\share" + ".lock" names the *share*
24+
// "\\\\server\\share.lock", a different network location entirely, so two
25+
// processes installing into the share would not share a lock.
26+
expect(installLockPath("\\\\server\\share\\")).toBe("\\\\server\\share\\.lock")
27+
expect(installLockPath("\\\\server\\share")).toBe("\\\\server\\share\\.lock")
28+
})
29+
30+
test("still strips a trailing separator on an ordinary directory", () => {
31+
expect(installLockPath("/home/u/drivers/")).toBe("/home/u/drivers.lock")
32+
expect(installLockPath("/home/u/drivers")).toBe("/home/u/drivers.lock")
33+
expect(installLockPath("\\\\server\\share\\drivers\\")).toBe("\\\\server\\share\\drivers.lock")
34+
})
35+
36+
test("a directory deeper than the share root is not treated as a root", () => {
37+
expect(installLockPath("\\\\server\\share\\a")).toBe("\\\\server\\share\\a.lock")
38+
})
39+
})
40+
41+
describe("quotedAbsolutePaths covers every absolute shape", () => {
42+
test("POSIX", () => {
43+
expect(quotedAbsolutePaths(`open '/usr/lib/node_modules/x/package.json'`)).toEqual([
44+
"/usr/lib/node_modules/x/package.json",
45+
])
46+
})
47+
48+
test("drive letter, both separators", () => {
49+
expect(quotedAbsolutePaths(`open "C:\\app\\node_modules\\x\\package.json"`)).toEqual([
50+
"C:\\app\\node_modules\\x\\package.json",
51+
])
52+
expect(quotedAbsolutePaths(`open "C:/app/node_modules/x/package.json"`)).toEqual([
53+
"C:/app/node_modules/x/package.json",
54+
])
55+
})
56+
57+
test("UNC share", () => {
58+
// Without the UNC alternative this yields nothing, so a driver on a share
59+
// stays unfindable even though the error named its exact location.
60+
expect(quotedAbsolutePaths(`ENOENT: open '\\\\server\\share\\node_modules\\duckdb\\package.json'`)).toEqual([
61+
"\\\\server\\share\\node_modules\\duckdb\\package.json",
62+
])
63+
})
64+
65+
test("ignores relative paths and bare words", () => {
66+
expect(quotedAbsolutePaths(`Cannot find package 'duckdb' from 'lib/x.js'`)).toEqual([])
67+
})
68+
})
69+
70+
describe("enclosingNodeModulesRoots on Windows separators", () => {
71+
test("walks back through a UNC path, innermost first", () => {
72+
const roots = enclosingNodeModulesRoots(
73+
"\\\\server\\share\\app\\node_modules\\a\\node_modules\\duckdb\\package.json",
74+
"\\",
75+
)
76+
expect(roots).toEqual([
77+
"\\\\server\\share\\app\\node_modules\\a\\node_modules",
78+
"\\\\server\\share\\app\\node_modules",
79+
])
80+
})
81+
})

0 commit comments

Comments
 (0)