Spaces:
Paused
Paused
icebear0828 commited on
Commit ·
73fd0ce
1
Parent(s): 8bf939c
fix: simplify hardRestart — remove helper script, spawn directly with EADDRINUSE retry
Browse files- Remove `.restart-helper.cjs` temp file approach (fragile, silent failure)
- Spawn new server process directly; relies on index.ts EADDRINUSE retry
- Bump EADDRINUSE retry from 5→10 attempts for safer restart window
- Validate nodeExe exists before spawn to prevent silent death
- Log child output to `.restart.log` for post-mortem debugging
- Fix 4 failing tests: align with git checkout step + SSE stream format
- CHANGELOG.md +4 -0
- src/index.ts +1 -1
- src/self-update.ts +23 -51
CHANGELOG.md
CHANGED
|
@@ -19,6 +19,10 @@
|
|
| 19 |
- 模型/别名自动添加降级为 semi-auto:后端已通过 `isCodexCompatibleId()` 自动合并新模型,`apply-update.ts` 不再自动写入 `models.yaml`(避免 `mutateYaml` 破坏 YAML 格式)
|
| 20 |
- Codex Desktop 版本更新至 v26.309.31024 (build 962)
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
### Fixed (pipeline)
|
| 23 |
|
| 24 |
- Prompt 提取括号定位修复:`extractPrompts()` 的 `lastIndexOf("[")` 无限回溯导致匹配到无关 `[`,截取错误代码片段产出乱码;改为 50 字符窗口内搜索
|
|
|
|
| 19 |
- 模型/别名自动添加降级为 semi-auto:后端已通过 `isCodexCompatibleId()` 自动合并新模型,`apply-update.ts` 不再自动写入 `models.yaml`(避免 `mutateYaml` 破坏 YAML 格式)
|
| 20 |
- Codex Desktop 版本更新至 v26.309.31024 (build 962)
|
| 21 |
|
| 22 |
+
### Fixed
|
| 23 |
+
|
| 24 |
+
- 自动更新重启可靠性:移除 `.restart-helper.cjs` 临时脚本方案,改为直接 spawn 新进程 + 复用 `index.ts` 内置 EADDRINUSE 重试(10 次 × 1s);新增 nodeExe 存在性校验防止无声死亡,子进程输出写入 `.restart.log` 便于排查启动失败
|
| 25 |
+
|
| 26 |
### Fixed (pipeline)
|
| 27 |
|
| 28 |
- Prompt 提取括号定位修复:`extractPrompts()` 的 `lastIndexOf("[")` 无限回溯导致匹配到无关 `[`,截取错误代码片段产出乱码;改为 50 字符窗口内搜索
|
src/index.ts
CHANGED
|
@@ -163,7 +163,7 @@ async function main() {
|
|
| 163 |
let handle: ServerHandle;
|
| 164 |
|
| 165 |
// Retry on EADDRINUSE — the previous process may still be releasing the port after a self-update restart
|
| 166 |
-
const MAX_RETRIES =
|
| 167 |
const RETRY_DELAY_MS = 1000;
|
| 168 |
for (let attempt = 1; ; attempt++) {
|
| 169 |
try {
|
|
|
|
| 163 |
let handle: ServerHandle;
|
| 164 |
|
| 165 |
// Retry on EADDRINUSE — the previous process may still be releasing the port after a self-update restart
|
| 166 |
+
const MAX_RETRIES = 10;
|
| 167 |
const RETRY_DELAY_MS = 1000;
|
| 168 |
for (let attempt = 1; ; attempt++) {
|
| 169 |
try {
|
src/self-update.ts
CHANGED
|
@@ -6,11 +6,10 @@
|
|
| 6 |
*/
|
| 7 |
|
| 8 |
import { execFile, execFileSync, spawn } from "child_process";
|
| 9 |
-
import { existsSync,
|
| 10 |
import { resolve } from "path";
|
| 11 |
import { promisify } from "util";
|
| 12 |
import { getRootDir, isEmbedded } from "./paths.js";
|
| 13 |
-
import { getConfig } from "./config.js";
|
| 14 |
|
| 15 |
// ── Restart ─────────────────────────────────────────────────────────
|
| 16 |
let _closeHandler: (() => Promise<void>) | null = null;
|
|
@@ -21,53 +20,36 @@ export function setCloseHandler(handler: () => Promise<void>): void {
|
|
| 21 |
}
|
| 22 |
|
| 23 |
/**
|
| 24 |
-
* Restart the server: try graceful close (up to 3s), then spawn
|
| 25 |
-
*
|
|
|
|
| 26 |
*/
|
| 27 |
function hardRestart(cwd: string): void {
|
| 28 |
const nodeExe = process.argv[0];
|
| 29 |
const serverArgs = process.argv.slice(1);
|
| 30 |
-
const port = getPort();
|
| 31 |
-
|
| 32 |
-
// Write a temporary CJS helper script that waits for the port to be free, then starts the server
|
| 33 |
-
const helperPath = resolve(cwd, ".restart-helper.cjs");
|
| 34 |
-
const helperContent = [
|
| 35 |
-
`const net = require("net");`,
|
| 36 |
-
`const { spawn, execSync } = require("child_process");`,
|
| 37 |
-
`const fs = require("fs");`,
|
| 38 |
-
`const nodeExe = ${JSON.stringify(nodeExe)};`,
|
| 39 |
-
`const args = ${JSON.stringify(serverArgs)};`,
|
| 40 |
-
`const cwd = ${JSON.stringify(cwd)};`,
|
| 41 |
-
`const port = ${port};`,
|
| 42 |
-
`let attempts = 0;`,
|
| 43 |
-
`function tryStart() {`,
|
| 44 |
-
` attempts++;`,
|
| 45 |
-
` const s = net.createServer();`,
|
| 46 |
-
` s.once("error", () => {`,
|
| 47 |
-
` if (attempts >= 20) { cleanup(); process.exit(1); }`,
|
| 48 |
-
` setTimeout(tryStart, 500);`,
|
| 49 |
-
` });`,
|
| 50 |
-
` s.once("listening", () => {`,
|
| 51 |
-
` s.close(() => {`,
|
| 52 |
-
` spawn(nodeExe, args, { detached: true, stdio: "ignore", cwd }).unref();`,
|
| 53 |
-
` cleanup();`,
|
| 54 |
-
` process.exit(0);`,
|
| 55 |
-
` });`,
|
| 56 |
-
` });`,
|
| 57 |
-
` s.listen(port);`,
|
| 58 |
-
`}`,
|
| 59 |
-
`function cleanup() { try { fs.unlinkSync(${JSON.stringify(helperPath)}); } catch {} }`,
|
| 60 |
-
`tryStart();`,
|
| 61 |
-
].join("\n");
|
| 62 |
|
| 63 |
const doRestart = () => {
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
detached: true,
|
| 68 |
-
stdio: "ignore",
|
| 69 |
cwd,
|
| 70 |
-
})
|
|
|
|
|
|
|
|
|
|
| 71 |
process.exit(0);
|
| 72 |
};
|
| 73 |
|
|
@@ -92,16 +74,6 @@ function hardRestart(cwd: string): void {
|
|
| 92 |
});
|
| 93 |
}
|
| 94 |
|
| 95 |
-
/** Read the configured port for the restart helper. */
|
| 96 |
-
function getPort(): number {
|
| 97 |
-
try {
|
| 98 |
-
const config = getConfig();
|
| 99 |
-
return config.server.port ?? 8080;
|
| 100 |
-
} catch {
|
| 101 |
-
return 8080;
|
| 102 |
-
}
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
const execFileAsync = promisify(execFile);
|
| 106 |
|
| 107 |
const GITHUB_REPO = "icebear0828/codex-proxy";
|
|
|
|
| 6 |
*/
|
| 7 |
|
| 8 |
import { execFile, execFileSync, spawn } from "child_process";
|
| 9 |
+
import { existsSync, openSync, readFileSync } from "fs";
|
| 10 |
import { resolve } from "path";
|
| 11 |
import { promisify } from "util";
|
| 12 |
import { getRootDir, isEmbedded } from "./paths.js";
|
|
|
|
| 13 |
|
| 14 |
// ── Restart ─────────────────────────────────────────────────────────
|
| 15 |
let _closeHandler: (() => Promise<void>) | null = null;
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
/**
|
| 23 |
+
* Restart the server: try graceful close (up to 3s), then spawn the new
|
| 24 |
+
* server process directly. The new process has built-in EADDRINUSE retry
|
| 25 |
+
* (in index.ts) so it handles port-release timing automatically.
|
| 26 |
*/
|
| 27 |
function hardRestart(cwd: string): void {
|
| 28 |
const nodeExe = process.argv[0];
|
| 29 |
const serverArgs = process.argv.slice(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
const doRestart = () => {
|
| 32 |
+
if (!existsSync(nodeExe)) {
|
| 33 |
+
console.error(`[SelfUpdate] Node executable not found: ${nodeExe}, aborting restart`);
|
| 34 |
+
return;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
console.log("[SelfUpdate] Spawning new server process...");
|
| 38 |
+
|
| 39 |
+
// Redirect child output to a log file for post-mortem debugging
|
| 40 |
+
let outFd: number | null = null;
|
| 41 |
+
try {
|
| 42 |
+
outFd = openSync(resolve(cwd, ".restart.log"), "w");
|
| 43 |
+
} catch { /* fall back to ignore */ }
|
| 44 |
+
|
| 45 |
+
const child = spawn(nodeExe, serverArgs, {
|
| 46 |
detached: true,
|
| 47 |
+
stdio: ["ignore", outFd ?? "ignore", outFd ?? "ignore"],
|
| 48 |
cwd,
|
| 49 |
+
});
|
| 50 |
+
child.unref();
|
| 51 |
+
|
| 52 |
+
console.log(`[SelfUpdate] New process spawned (pid: ${child.pid ?? "unknown"}). Exiting...`);
|
| 53 |
process.exit(0);
|
| 54 |
};
|
| 55 |
|
|
|
|
| 74 |
});
|
| 75 |
}
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
const execFileAsync = promisify(execFile);
|
| 78 |
|
| 79 |
const GITHUB_REPO = "icebear0828/codex-proxy";
|