Skip to content

Commit cb61a92

Browse files
authored
Merge pull request #1025 from ilysenko/codex/fix-node-test-hangs
fix(ci): prevent Node test hangs
2 parents c36fa57 + 51d7c0c commit cb61a92

6 files changed

Lines changed: 126 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ jobs:
1919
rust-and-smoke:
2020
name: Rust and Smoke Tests
2121
runs-on: ubuntu-latest
22+
timeout-minutes: 30
2223
steps:
2324
- uses: actions/checkout@v7
2425

26+
- uses: actions/setup-node@v7
27+
with:
28+
node-version: 24
29+
2530
- name: Install Rust components
2631
run: rustup component add rustfmt clippy
2732

@@ -38,6 +43,7 @@ jobs:
3843
bash -n scripts/ci/validate-nix-pins.sh
3944
4045
- name: Check Node syntax and tests
46+
timeout-minutes: 10
4147
run: |
4248
set -euo pipefail
4349
bash scripts/ci/run-node-checks.sh

linux-features/global-dictation/test.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const hostPathEntries = (process.env.PATH ?? "")
2525
.split(path.delimiter)
2626
.filter((entry) => path.isAbsolute(entry));
2727
const hostPath = [...new Set(hostPathEntries)].join(path.delimiter);
28+
const X11_RELEASE_MONITOR_TEST_TIMEOUT_MS = 3000;
2829

2930
function findHostExecutable(name) {
3031
const executable = hostPathEntries
@@ -115,7 +116,13 @@ function writeExecutable(filePath, body) {
115116
fs.writeFileSync(filePath, body, { mode: 0o755 });
116117
}
117118

118-
function runX11ReleaseMonitor({ accelerator, xmodmapLines, queryStateLines, testLines, timeout = 1000 }) {
119+
function runX11ReleaseMonitor({
120+
accelerator,
121+
xmodmapLines,
122+
queryStateLines,
123+
testLines,
124+
timeout = X11_RELEASE_MONITOR_TEST_TIMEOUT_MS,
125+
}) {
119126
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "global-dictation-x11-map-"));
120127
const binDir = path.join(tempDir, "bin");
121128
fs.mkdirSync(binDir);
@@ -411,7 +418,7 @@ test("X11 release watcher exits when the primary key is released while the modif
411418
DISPLAY: ":99",
412419
PATH: `${binDir}${path.delimiter}${hostPath}`,
413420
},
414-
timeout: 1000,
421+
timeout: X11_RELEASE_MONITOR_TEST_TIMEOUT_MS,
415422
},
416423
);
417424
assert.equal(result.status, 0, result.stderr);
@@ -453,7 +460,7 @@ test("X11 release watcher prefers resolved keycodes over fallback codes", () =>
453460
DISPLAY: ":99",
454461
PATH: `${binDir}${path.delimiter}${hostPath}`,
455462
},
456-
timeout: 1000,
463+
timeout: X11_RELEASE_MONITOR_TEST_TIMEOUT_MS,
457464
},
458465
);
459466
assert.equal(result.status, 0, result.stderr);

linux-features/remote-mobile-control/patch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function linuxDeviceKeyProviderSource({ childProcessVar, cryptoVar, fsVar, pathV
133133
"}",
134134
`function codexLinuxRemoteControlOpenKeyStoreLock(){let e=codexLinuxRemoteControlDeviceKeyStorePath()+\`.lock\`,t,n=!1;try{try{t=${fsVar}.openSync(e,${fsVar}.constants.O_RDWR|${fsVar}.constants.O_CREAT|${fsVar}.constants.O_EXCL|${fsVar}.constants.O_NOFOLLOW,384),n=!0}catch(r){if(r?.code!==\`EEXIST\`)throw r;t=${fsVar}.openSync(e,${fsVar}.constants.O_RDWR|${fsVar}.constants.O_NOFOLLOW)}n&&${fsVar}.fchmodSync(t,384),codexLinuxRemoteControlAssertOwnedRegularStat(${fsVar}.fstatSync(t));return t}catch(r){try{t!=null&&${fsVar}.closeSync(t)}catch{}throw r}}`,
135135
`function codexLinuxRemoteControlResolveExecutable(codexLinuxRemoteControlExecutableName){let codexLinuxRemoteControlSearchDirectories=[...(process.env.PATH??\`\`).split(${pathVar}.delimiter),\`/run/current-system/sw/bin\`,\`/nix/var/nix/profiles/default/bin\`,\`/usr/local/bin\`,\`/usr/bin\`,\`/bin\`],codexLinuxRemoteControlSeenDirectories=new Set;for(let codexLinuxRemoteControlDirectory of codexLinuxRemoteControlSearchDirectories){if(!codexLinuxRemoteControlDirectory||!${pathVar}.isAbsolute(codexLinuxRemoteControlDirectory)||codexLinuxRemoteControlSeenDirectories.has(codexLinuxRemoteControlDirectory))continue;codexLinuxRemoteControlSeenDirectories.add(codexLinuxRemoteControlDirectory);try{let codexLinuxRemoteControlExecutablePath=${fsVar}.realpathSync(${pathVar}.join(codexLinuxRemoteControlDirectory,codexLinuxRemoteControlExecutableName)),codexLinuxRemoteControlExecutableStat=${fsVar}.statSync(codexLinuxRemoteControlExecutablePath);if(!codexLinuxRemoteControlExecutableStat.isFile())continue;${fsVar}.accessSync(codexLinuxRemoteControlExecutablePath,${fsVar}.constants.X_OK);return codexLinuxRemoteControlExecutablePath}catch{}}return null}`,
136-
`function codexLinuxWithRemoteControlKeyStoreLock(codexLinuxRemoteControlLockedOperation){let codexLinuxRemoteControlLockFd=codexLinuxRemoteControlOpenKeyStoreLock(),codexLinuxRemoteControlFlockPath=codexLinuxRemoteControlResolveExecutable(\`flock\`),codexLinuxRemoteControlShellPath=codexLinuxRemoteControlResolveExecutable(\`sh\`);if(codexLinuxRemoteControlFlockPath==null||codexLinuxRemoteControlShellPath==null){${fsVar}.closeSync(codexLinuxRemoteControlLockFd);throw Error(\`Linux remote control device key store requires flock and sh\`)}return new Promise((codexLinuxRemoteControlResolve,codexLinuxRemoteControlReject)=>{let codexLinuxRemoteControlLockProcess;try{codexLinuxRemoteControlLockProcess=${childProcessVar}.spawn(codexLinuxRemoteControlFlockPath,[\`-x\`,\`-w\`,\`5\`,\`/proc/self/fd/3\`,codexLinuxRemoteControlShellPath,\`-c\`,\`printf 'ready\\n'; cat >/dev/null\`],{stdio:[\`pipe\`,\`pipe\`,\`pipe\`,codexLinuxRemoteControlLockFd]})}finally{${fsVar}.closeSync(codexLinuxRemoteControlLockFd)}let codexLinuxRemoteControlStdout=\`\`,codexLinuxRemoteControlStderr=\`\`,codexLinuxRemoteControlResult,codexLinuxRemoteControlReady=!1,codexLinuxRemoteControlOperationDone=!1,codexLinuxRemoteControlProcessDone=!1,codexLinuxRemoteControlExitCode=null,codexLinuxRemoteControlFailure=null,codexLinuxRemoteControlTimer=setTimeout(()=>{codexLinuxRemoteControlReady||(codexLinuxRemoteControlFailure=Error(\`Timed out waiting for Linux remote control device key store lock\`),codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.kill())},5500),codexLinuxRemoteControlSettle=()=>{if(!codexLinuxRemoteControlOperationDone||!codexLinuxRemoteControlProcessDone)return;codexLinuxRemoteControlFailure?codexLinuxRemoteControlReject(codexLinuxRemoteControlFailure):codexLinuxRemoteControlExitCode===0?codexLinuxRemoteControlResolve(codexLinuxRemoteControlResult):codexLinuxRemoteControlReject(Error(\`Linux remote control device key store lock failed\`))};codexLinuxRemoteControlLockProcess.stderr.on(\`data\`,codexLinuxRemoteControlChunk=>{codexLinuxRemoteControlStderr=(codexLinuxRemoteControlStderr+String(codexLinuxRemoteControlChunk)).slice(-4096)}),codexLinuxRemoteControlLockProcess.on(\`error\`,codexLinuxRemoteControlError=>{clearTimeout(codexLinuxRemoteControlTimer),codexLinuxRemoteControlFailure=codexLinuxRemoteControlError,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlProcessDone=!0,codexLinuxRemoteControlSettle()}),codexLinuxRemoteControlLockProcess.on(\`exit\`,codexLinuxRemoteControlCode=>{clearTimeout(codexLinuxRemoteControlTimer),codexLinuxRemoteControlExitCode=codexLinuxRemoteControlCode,codexLinuxRemoteControlProcessDone=!0,codexLinuxRemoteControlReady||(codexLinuxRemoteControlFailure=Error(codexLinuxRemoteControlStderr.trim()||\`Timed out waiting for Linux remote control device key store lock\`),codexLinuxRemoteControlOperationDone=!0),codexLinuxRemoteControlSettle()}),codexLinuxRemoteControlLockProcess.stdout.on(\`data\`,codexLinuxRemoteControlChunk=>{if(codexLinuxRemoteControlReady)return;codexLinuxRemoteControlStdout+=String(codexLinuxRemoteControlChunk);if(!codexLinuxRemoteControlStdout.includes(\`ready\\n\`))return;codexLinuxRemoteControlReady=!0,clearTimeout(codexLinuxRemoteControlTimer),Promise.resolve().then(codexLinuxRemoteControlLockedOperation).then(codexLinuxRemoteControlValue=>{codexLinuxRemoteControlResult=codexLinuxRemoteControlValue,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.stdin.end(),codexLinuxRemoteControlSettle()},codexLinuxRemoteControlError=>{codexLinuxRemoteControlFailure=codexLinuxRemoteControlError,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.stdin.end(),codexLinuxRemoteControlSettle()})})})}`,
136+
`function codexLinuxWithRemoteControlKeyStoreLock(codexLinuxRemoteControlLockedOperation){let codexLinuxRemoteControlLockFd=codexLinuxRemoteControlOpenKeyStoreLock(),codexLinuxRemoteControlFlockPath=codexLinuxRemoteControlResolveExecutable(\`flock\`),codexLinuxRemoteControlShellPath=codexLinuxRemoteControlResolveExecutable(\`sh\`);if(codexLinuxRemoteControlFlockPath==null||codexLinuxRemoteControlShellPath==null){${fsVar}.closeSync(codexLinuxRemoteControlLockFd);throw Error(\`Linux remote control device key store requires flock and sh\`)}return new Promise((codexLinuxRemoteControlResolve,codexLinuxRemoteControlReject)=>{let codexLinuxRemoteControlLockProcess;try{codexLinuxRemoteControlLockProcess=${childProcessVar}.spawn(codexLinuxRemoteControlFlockPath,[\`-x\`,\`-w\`,\`5\`,\`/proc/self/fd/3\`,codexLinuxRemoteControlShellPath,\`-c\`,\`printf 'ready\\n'; cat >/dev/null\`],{stdio:[\`pipe\`,\`pipe\`,\`pipe\`,codexLinuxRemoteControlLockFd]})}finally{${fsVar}.closeSync(codexLinuxRemoteControlLockFd)}let codexLinuxRemoteControlStdout=\`\`,codexLinuxRemoteControlStderr=\`\`,codexLinuxRemoteControlResult,codexLinuxRemoteControlReady=!1,codexLinuxRemoteControlOperationDone=!1,codexLinuxRemoteControlProcessDone=!1,codexLinuxRemoteControlExitCode=null,codexLinuxRemoteControlFailure=null,codexLinuxRemoteControlTimer=setTimeout(()=>{codexLinuxRemoteControlReady||(codexLinuxRemoteControlFailure=Error(\`Timed out waiting for Linux remote control device key store lock\`),codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.kill())},5500),codexLinuxRemoteControlSettle=()=>{if(!codexLinuxRemoteControlOperationDone||!codexLinuxRemoteControlProcessDone)return;codexLinuxRemoteControlFailure?codexLinuxRemoteControlReject(codexLinuxRemoteControlFailure):codexLinuxRemoteControlExitCode===0?codexLinuxRemoteControlResolve(codexLinuxRemoteControlResult):codexLinuxRemoteControlReject(Error(\`Linux remote control device key store lock failed\`))};codexLinuxRemoteControlLockProcess.stderr.on(\`data\`,codexLinuxRemoteControlChunk=>{codexLinuxRemoteControlStderr=(codexLinuxRemoteControlStderr+String(codexLinuxRemoteControlChunk)).slice(-4096)}),codexLinuxRemoteControlLockProcess.on(\`error\`,codexLinuxRemoteControlError=>{clearTimeout(codexLinuxRemoteControlTimer),codexLinuxRemoteControlFailure=codexLinuxRemoteControlError,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlProcessDone=!0,codexLinuxRemoteControlSettle()}),codexLinuxRemoteControlLockProcess.on(\`close\`,codexLinuxRemoteControlCode=>{clearTimeout(codexLinuxRemoteControlTimer),codexLinuxRemoteControlExitCode=codexLinuxRemoteControlCode,codexLinuxRemoteControlProcessDone=!0,codexLinuxRemoteControlReady||(codexLinuxRemoteControlFailure=Error(codexLinuxRemoteControlStderr.trim()||\`Timed out waiting for Linux remote control device key store lock\`),codexLinuxRemoteControlOperationDone=!0),codexLinuxRemoteControlSettle()}),codexLinuxRemoteControlLockProcess.stdout.on(\`data\`,codexLinuxRemoteControlChunk=>{if(codexLinuxRemoteControlReady)return;codexLinuxRemoteControlStdout+=String(codexLinuxRemoteControlChunk);if(!codexLinuxRemoteControlStdout.includes(\`ready\\n\`))return;codexLinuxRemoteControlReady=!0,clearTimeout(codexLinuxRemoteControlTimer),Promise.resolve().then(codexLinuxRemoteControlLockedOperation).then(codexLinuxRemoteControlValue=>{codexLinuxRemoteControlResult=codexLinuxRemoteControlValue,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.stdin.end(),codexLinuxRemoteControlSettle()},codexLinuxRemoteControlError=>{codexLinuxRemoteControlFailure=codexLinuxRemoteControlError,codexLinuxRemoteControlOperationDone=!0,codexLinuxRemoteControlLockProcess.stdin.end(),codexLinuxRemoteControlSettle()})})})}`,
137137
"function codexLinuxWriteRemoteControlDeviceKeyStore(codexLinuxRemoteControlKeyStore){",
138138
`codexLinuxRemoteControlValidateDeviceKeyStore(codexLinuxRemoteControlKeyStore);let codexLinuxRemoteControlKeyStorePath=codexLinuxRemoteControlDeviceKeyStorePath(),codexLinuxRemoteControlKeyStoreDirectory=${pathVar}.dirname(codexLinuxRemoteControlKeyStorePath),codexLinuxRemoteControlTempPath=codexLinuxRemoteControlKeyStorePath+\`.tmp-\`+${cryptoVar}.randomUUID(),codexLinuxRemoteControlKeyStoreText=JSON.stringify(codexLinuxRemoteControlKeyStore,null,2)+\`\\n\`,codexLinuxRemoteControlTempFd=null,codexLinuxRemoteControlDirectoryFd=null,codexLinuxRemoteControlTempCreated=!1;if(Buffer.byteLength(codexLinuxRemoteControlKeyStoreText,\`utf8\`)>codexLinuxRemoteControlKeyStoreMaxBytes)throw Error(\`Linux remote control device key store exceeds size limit\`);`,
139139
`try{codexLinuxRemoteControlTempFd=${fsVar}.openSync(codexLinuxRemoteControlTempPath,${fsVar}.constants.O_WRONLY|${fsVar}.constants.O_CREAT|${fsVar}.constants.O_EXCL|${fsVar}.constants.O_NOFOLLOW,384),codexLinuxRemoteControlTempCreated=!0,${fsVar}.writeFileSync(codexLinuxRemoteControlTempFd,codexLinuxRemoteControlKeyStoreText,\`utf8\`),${fsVar}.fsyncSync(codexLinuxRemoteControlTempFd),${fsVar}.closeSync(codexLinuxRemoteControlTempFd),codexLinuxRemoteControlTempFd=null;${fsVar}.existsSync(codexLinuxRemoteControlKeyStorePath)&&codexLinuxRemoteControlAssertOwnedRegularFile(codexLinuxRemoteControlKeyStorePath,${fsVar});${fsVar}.renameSync(codexLinuxRemoteControlTempPath,codexLinuxRemoteControlKeyStorePath),codexLinuxRemoteControlDirectoryFd=${fsVar}.openSync(codexLinuxRemoteControlKeyStoreDirectory,${fsVar}.constants.O_RDONLY),${fsVar}.fsyncSync(codexLinuxRemoteControlDirectoryFd),${fsVar}.closeSync(codexLinuxRemoteControlDirectoryFd),codexLinuxRemoteControlDirectoryFd=null}catch(codexLinuxRemoteControlWriteError){try{codexLinuxRemoteControlTempFd!=null&&${fsVar}.closeSync(codexLinuxRemoteControlTempFd)}catch{}try{codexLinuxRemoteControlDirectoryFd!=null&&${fsVar}.closeSync(codexLinuxRemoteControlDirectoryFd)}catch{}try{codexLinuxRemoteControlTempCreated&&${fsVar}.rmSync(codexLinuxRemoteControlTempPath,{force:!0})}catch{}throw codexLinuxRemoteControlWriteError}`,

linux-features/remote-mobile-control/test.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
const assert = require("node:assert/strict");
55
const { spawn, spawnSync } = require("node:child_process");
6+
const { EventEmitter, once } = require("node:events");
67
const fs = require("node:fs");
78
const os = require("node:os");
89
const path = require("node:path");
@@ -2906,16 +2907,54 @@ test("Linux device-key store serializes concurrent updates", async () => {
29062907
}
29072908
});
29082909

2909-
test("Linux device-key store contends on its validated lock file", async () => {
2910+
test("Linux device-key operations wait for lock process stdio to close", async () => {
2911+
const configHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-remote-mobile-key-close-"));
2912+
try {
2913+
const child = new EventEmitter();
2914+
child.stdin = { end() {} };
2915+
child.stdout = new EventEmitter();
2916+
child.stderr = new EventEmitter();
2917+
child.kill = () => true;
2918+
2919+
const client = createPatchedDeviceKeyClient(configHome, {
2920+
"node:child_process": {
2921+
spawn() {
2922+
return child;
2923+
},
2924+
},
2925+
});
2926+
let settled = false;
2927+
const creation = client.createDeviceKey("allow_os_protected_nonextractable").then((value) => {
2928+
settled = true;
2929+
return value;
2930+
});
2931+
2932+
child.stdout.emit("data", Buffer.from("ready\n"));
2933+
await new Promise((resolve) => setImmediate(resolve));
2934+
child.emit("exit", 0, null);
2935+
await new Promise((resolve) => setImmediate(resolve));
2936+
2937+
assert.equal(settled, false, "the lock operation must not resolve before child stdio closes");
2938+
2939+
child.emit("close", 0, null);
2940+
await creation;
2941+
} finally {
2942+
fs.rmSync(configHome, { recursive: true, force: true });
2943+
}
2944+
});
2945+
2946+
test("Linux device-key store contends on its validated lock file", { timeout: 10_000 }, async () => {
29102947
const configHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-remote-mobile-key-lock-"));
29112948
let holder;
2949+
let holderClosed;
29122950
try {
29132951
const client = createPatchedDeviceKeyClient(configHome);
29142952
await client.createDeviceKey("test");
29152953
const { lock } = remoteControlKeyStorePaths(configHome);
29162954
holder = spawn("flock", ["-x", lock, "sh", "-c", "printf 'ready\\n'; sleep 0.25"], {
29172955
stdio: ["ignore", "pipe", "pipe"],
29182956
});
2957+
holderClosed = once(holder, "close");
29192958
await new Promise((resolve, reject) => {
29202959
let output = "";
29212960
holder.once("error", reject);
@@ -2927,9 +2966,14 @@ test("Linux device-key store contends on its validated lock file", async () => {
29272966

29282967
const startedAt = Date.now();
29292968
await client.createDeviceKey("test");
2969+
const [holderExitCode] = await holderClosed;
29302970
assert.ok(Date.now() - startedAt >= 150, "key update must wait for the existing file lock");
2971+
assert.equal(holderExitCode, 0);
29312972
} finally {
2932-
holder?.kill();
2973+
if (holder && holder.exitCode == null && holder.signalCode == null) {
2974+
holder.kill("SIGKILL");
2975+
}
2976+
await holderClosed?.catch(() => {});
29332977
fs.rmSync(configHome, { recursive: true, force: true });
29342978
}
29352979
});

linux-features/shared-app-server-socket/test.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const assert = require("node:assert/strict");
55
const { spawn, spawnSync } = require("node:child_process");
6-
const { EventEmitter } = require("node:events");
6+
const { EventEmitter, once } = require("node:events");
77
const fs = require("node:fs");
88
const net = require("node:net");
99
const os = require("node:os");
@@ -304,7 +304,21 @@ test("injected transport serializes startup and removes only its owned socket",
304304
const children = [];
305305
let replacement;
306306
let installReplacementBeforeClose = false;
307+
const identityFs = {
308+
...fs,
309+
lstatSync(candidate, ...args) {
310+
const stat = fs.lstatSync(candidate, ...args);
311+
if (candidate !== socketPath || !installReplacementBeforeClose) return stat;
312+
return new Proxy(stat, {
313+
get(target, property, receiver) {
314+
if (property === "ino") return target.ino + 1;
315+
return Reflect.get(target, property, receiver);
316+
},
317+
});
318+
},
319+
};
307320
const { Transport } = loadInjectedTransport({
321+
fsImpl: identityFs,
308322
spawnImpl(_command, args) {
309323
const child = fakeChild();
310324
children.push(child);
@@ -335,8 +349,9 @@ test("injected transport serializes startup and removes only its owned socket",
335349
await assert.rejects(second.ensureAuthority(), /already owned/);
336350

337351
installReplacementBeforeClose = true;
352+
const childClosed = once(children[0], "close");
338353
first.dispose();
339-
await new Promise((resolve) => setTimeout(resolve, 50));
354+
await childClosed;
340355
assert.equal(fs.lstatSync(socketPath).isSocket(), true, "replacement socket must survive dispose");
341356
await closeServer(replacement);
342357
} finally {

0 commit comments

Comments
 (0)