@qt4798/pi-web 0.14.4 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +22 -22
- package/.next/build-manifest.json +2 -2
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/app-update/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +22 -22
- package/.next/server/chunks/7915.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/app/{page-bcc0e2ed5b5f6035.js → page-54c57e029a68ffd4.js} +1 -1
- package/.next/trace +7 -8
- package/.next/trace-build +1 -1
- package/bin/memory-watchdog.js +135 -0
- package/bin/pi-web.js +8 -2
- package/bin/watchdog-preload.js +20 -0
- package/package.json +1 -1
- /package/.next/static/{h9xeO9wlQinP87V3yuXj6 → 7xdY7qoeKsF7B1usdcD8R}/_buildManifest.js +0 -0
- /package/.next/static/{h9xeO9wlQinP87V3yuXj6 → 7xdY7qoeKsF7B1usdcD8R}/_ssgManifest.js +0 -0
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-webpack","duration":
|
|
1
|
+
[{"name":"run-webpack","duration":24268003,"timestamp":171763054687,"id":14,"parentId":1,"tags":{},"startTime":1788019938891,"traceId":"b5c8c000f3d6e835"},{"name":"run-typescript","duration":4511201,"timestamp":171787325982,"id":865,"parentId":1,"tags":{},"startTime":1788019963163,"traceId":"b5c8c000f3d6e835"},{"name":"static-check","duration":3333877,"timestamp":171791847649,"id":868,"parentId":1,"tags":{},"startTime":1788019967685,"traceId":"b5c8c000f3d6e835"},{"name":"static-generation","duration":3878156,"timestamp":171795418581,"id":1052,"parentId":1,"tags":{},"startTime":1788019971256,"traceId":"b5c8c000f3d6e835"},{"name":"collect-build-traces","duration":26656691,"timestamp":171795184673,"id":1049,"parentId":1,"tags":{},"startTime":1788019971022,"traceId":"b5c8c000f3d6e835"},{"name":"output-standalone","duration":2424887,"timestamp":171821843015,"id":1060,"parentId":1,"tags":{},"startTime":1788019997681,"traceId":"b5c8c000f3d6e835"},{"name":"telemetry-flush","duration":34,"timestamp":171824273008,"id":1063,"parentId":1,"tags":{},"startTime":1788020000111,"traceId":"b5c8c000f3d6e835"},{"name":"next-build","duration":62105703,"timestamp":171762167345,"id":1,"tags":{"buildMode":"default","version":"16.3.1","bundler":"webpack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1788019938003,"traceId":"b5c8c000f3d6e835"}]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Idle memory watchdog for the next-server process. V8 only returns freed
|
|
4
|
+
// heap to the OS on process exit, so a long-running server's RSS settles at
|
|
5
|
+
// its high-water mark no matter how much GC reclaims internally. This module
|
|
6
|
+
// watches RSS and exits the process once it has stayed above the threshold
|
|
7
|
+
// while no agent session is running; the supervisor (launchd KeepAlive) then
|
|
8
|
+
// starts a fresh process.
|
|
9
|
+
|
|
10
|
+
const DEFAULT_RESTART_MB = 1229;
|
|
11
|
+
const DEFAULT_CHECK_MINUTES = 5;
|
|
12
|
+
const CONSECUTIVE_OVER_LIMIT = 2;
|
|
13
|
+
const STARTUP_GRACE_TICKS = 3;
|
|
14
|
+
const RUNNING_CHECK_TIMEOUT_MS = 5000;
|
|
15
|
+
|
|
16
|
+
function parsePositiveInt(raw, fallback, minimum) {
|
|
17
|
+
const n = Number.parseInt(raw ?? "", 10);
|
|
18
|
+
if (!Number.isFinite(n) || n < minimum) return fallback;
|
|
19
|
+
return n;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the watchdog config from env. Returns null when the watchdog must
|
|
24
|
+
* not run: explicitly disabled (PI_WEB_RSS_RESTART_MB=0), running on Windows
|
|
25
|
+
* (no launchd-style supervisor to restart us, and auto-restart is not wanted
|
|
26
|
+
* there), or no port known (without a port we cannot ask /api/agent/running
|
|
27
|
+
* and must never exit).
|
|
28
|
+
*/
|
|
29
|
+
function resolveConfig(env) {
|
|
30
|
+
if (process.platform === "win32") return null;
|
|
31
|
+
const rawRestart = env.PI_WEB_RSS_RESTART_MB;
|
|
32
|
+
if (rawRestart !== undefined && rawRestart !== "" && Number(rawRestart) === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const port = Number.parseInt(env.PI_WEB_PORT ?? "", 10);
|
|
36
|
+
if (!Number.isFinite(port) || port <= 0) return null;
|
|
37
|
+
return {
|
|
38
|
+
port,
|
|
39
|
+
hostname: env.PI_WEB_HOSTNAME || "127.0.0.1",
|
|
40
|
+
restartMb: parsePositiveInt(rawRestart, DEFAULT_RESTART_MB, 1),
|
|
41
|
+
checkMinutes: parsePositiveInt(env.PI_WEB_RSS_CHECK_MINUTES, DEFAULT_CHECK_MINUTES, 1),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function basicAuthHeaders(env) {
|
|
46
|
+
// Next.js loads PI_WEB_PASSWORD from .env in production, and the proxy gate
|
|
47
|
+
// rejects unauthenticated /api calls. Reuse the credential this process
|
|
48
|
+
// already holds so the loopback check passes without weakening auth.
|
|
49
|
+
const password = env.PI_WEB_PASSWORD;
|
|
50
|
+
if (!password) return undefined;
|
|
51
|
+
return { authorization: `Basic ${Buffer.from(`pi:${password}`).toString("base64")}` };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function isServerIdle(config, env) {
|
|
55
|
+
const res = await fetch(`http://${config.hostname}:${config.port}/api/agent/running`, {
|
|
56
|
+
headers: basicAuthHeaders(env),
|
|
57
|
+
signal: AbortSignal.timeout(RUNNING_CHECK_TIMEOUT_MS),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) return null;
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
const ids = Array.isArray(data && data.runningSessionIds) ? data.runningSessionIds : [];
|
|
62
|
+
return ids.length === 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Start the watchdog loop in the current process. Never throws; failures to
|
|
67
|
+
* check are skipped conservatively (streak reset) so a transient error can
|
|
68
|
+
* never cause an exit.
|
|
69
|
+
*/
|
|
70
|
+
function startMemoryWatchdog(log = defaultLog) {
|
|
71
|
+
const config = resolveConfig(process.env);
|
|
72
|
+
if (!config) return null;
|
|
73
|
+
|
|
74
|
+
let ticks = 0;
|
|
75
|
+
let overLimitStreak = 0;
|
|
76
|
+
let loggedNonIdle = false;
|
|
77
|
+
log(`watching RSS every ${config.checkMinutes}min, restart at >= ${config.restartMb}MB when idle (grace: ${STARTUP_GRACE_TICKS} ticks)`);
|
|
78
|
+
|
|
79
|
+
const timer = setInterval(() => {
|
|
80
|
+
ticks += 1;
|
|
81
|
+
const rssMb = process.memoryUsage().rss / (1024 * 1024);
|
|
82
|
+
if (ticks <= STARTUP_GRACE_TICKS || rssMb < config.restartMb) {
|
|
83
|
+
overLimitStreak = 0;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
isServerIdle(config, process.env)
|
|
87
|
+
.then((idle) => {
|
|
88
|
+
if (idle !== true) {
|
|
89
|
+
// Sessions running, or the check failed — never exit on unknown state.
|
|
90
|
+
if (!loggedNonIdle) {
|
|
91
|
+
loggedNonIdle = true;
|
|
92
|
+
log(`over limit (${Math.round(rssMb)}MB) but idle check says ${idle === null ? "unavailable (check failed)" : "sessions running"}; not restarting`);
|
|
93
|
+
}
|
|
94
|
+
overLimitStreak = 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
overLimitStreak += 1;
|
|
98
|
+
log(`RSS ${Math.round(rssMb)}MB >= ${config.restartMb}MB and idle (${overLimitStreak}/${CONSECUTIVE_OVER_LIMIT})`);
|
|
99
|
+
if (overLimitStreak >= CONSECUTIVE_OVER_LIMIT) {
|
|
100
|
+
clearInterval(timer);
|
|
101
|
+
log("exiting so the supervisor can restart us and reclaim memory");
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.catch((err) => {
|
|
106
|
+
if (!loggedNonIdle) {
|
|
107
|
+
loggedNonIdle = true;
|
|
108
|
+
log(`over limit but idle check errored: ${err && err.message}; not restarting`);
|
|
109
|
+
}
|
|
110
|
+
overLimitStreak = 0;
|
|
111
|
+
});
|
|
112
|
+
}, config.checkMinutes * 60 * 1000);
|
|
113
|
+
// The server itself keeps the process alive; the watchdog must never be the
|
|
114
|
+
// reason a process stays up.
|
|
115
|
+
timer.unref();
|
|
116
|
+
return timer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function defaultLog(message) {
|
|
120
|
+
console.log(`[memory-watchdog] ${message}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { startMemoryWatchdog, resolveConfig };
|
|
124
|
+
|
|
125
|
+
// CLI: `node bin/memory-watchdog.js` runs the loop in-process (smoke tests).
|
|
126
|
+
// The interval is unref'd for server use, so re-ref it to keep this process
|
|
127
|
+
// alive.
|
|
128
|
+
if (require.main === module) {
|
|
129
|
+
const timer = startMemoryWatchdog();
|
|
130
|
+
if (!timer) {
|
|
131
|
+
console.log("[memory-watchdog] disabled (PI_WEB_RSS_RESTART_MB=0 or no PI_WEB_PORT)");
|
|
132
|
+
} else {
|
|
133
|
+
timer.ref();
|
|
134
|
+
}
|
|
135
|
+
}
|
package/bin/pi-web.js
CHANGED
|
@@ -48,6 +48,12 @@ const { port, hostname, openBrowser } = parseLaunchOptions();
|
|
|
48
48
|
const { mergedNodeOptions } = require("./with-memory-limit");
|
|
49
49
|
process.env.NODE_OPTIONS = mergedNodeOptions(process.env.NODE_OPTIONS);
|
|
50
50
|
|
|
51
|
+
// Idle memory watchdog, loaded only into the next-server process. Injected as
|
|
52
|
+
// a node CLI arg (not NODE_OPTIONS) so agent-spawned child processes never
|
|
53
|
+
// inherit it. Disabled with PI_WEB_RSS_RESTART_MB=0; the module itself also
|
|
54
|
+
// refuses to run when it doesn't know the port to ask about running sessions.
|
|
55
|
+
const watchdogPreload = path.join(__dirname, "watchdog-preload.js");
|
|
56
|
+
|
|
51
57
|
if (!fs.existsSync(nextDir)) {
|
|
52
58
|
console.error("Build artifacts not found. Please report this issue.");
|
|
53
59
|
process.exit(1);
|
|
@@ -71,10 +77,10 @@ const nextArgs = ["start", "-p", port, "-H", hostname];
|
|
|
71
77
|
|
|
72
78
|
// Always run next's JS entry with node directly — avoids .bin symlink issues
|
|
73
79
|
// and path-with-spaces problems on Windows when shell: true is used.
|
|
74
|
-
const child = spawn(process.execPath, [nextBin, ...nextArgs], {
|
|
80
|
+
const child = spawn(process.execPath, ["--require", watchdogPreload, nextBin, ...nextArgs], {
|
|
75
81
|
cwd: pkgDir,
|
|
76
82
|
stdio: ["inherit", "pipe", "inherit"],
|
|
77
|
-
env: { ...process.env, PI_WEB_HOSTNAME: hostname },
|
|
83
|
+
env: { ...process.env, PI_WEB_HOSTNAME: hostname, PI_WEB_PORT: String(port) },
|
|
78
84
|
});
|
|
79
85
|
wireChildProcessLifecycle(child);
|
|
80
86
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Loaded into the next-server process via `node --require bin/watchdog-preload.js
|
|
4
|
+
// next start ...` (wired up in pi-web.js). Startup is deferred so a problem
|
|
5
|
+
// here can never delay or break server boot — worst case is one warn line and
|
|
6
|
+
// no watchdog.
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
10
|
+
const { startMemoryWatchdog } = require("./memory-watchdog");
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
try {
|
|
13
|
+
startMemoryWatchdog();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.warn(`[memory-watchdog] failed to start: ${err && err.message}`);
|
|
16
|
+
}
|
|
17
|
+
}, 3000).unref();
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.warn(`[memory-watchdog] preload failed: ${err && err.message}`);
|
|
20
|
+
}
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|