Fix memory leak in dev server
This commit is contained in:
+58
-19
@@ -1,6 +1,6 @@
|
||||
import chokidar from "chokidar";
|
||||
import path from "path";
|
||||
import { existsSync } from "fs";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import fullRebuild from "./full-rebuild";
|
||||
import { AppData } from "../../data/app-data";
|
||||
@@ -10,10 +10,18 @@ import grabAllPages from "../../utils/grab-all-pages";
|
||||
import serverPostBuildFn from "./server-post-build-fn";
|
||||
import { log } from "../../utils/log";
|
||||
import { logBuildErrorsOnce } from "../../utils/build-error-log";
|
||||
const { ROOT_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
|
||||
// Tracks the last watcher re-creation so the 5s watchdog in start-server
|
||||
// doesn't fight fullRebuild (which also restarts the watcher after a rebuild).
|
||||
const { ROOT_DIR, BUNX_CWD_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
|
||||
// The watchdog writes this file and expects the watcher to report it. It
|
||||
// sits under `.bunext` (otherwise ignored), so only this path is watched.
|
||||
const WATCH_PROBE_DIR = path.join(BUNX_CWD_DIR, "watch-probe");
|
||||
const WATCH_PROBE_FILE = path.join(WATCH_PROBE_DIR, "probe");
|
||||
// Dedupes near-simultaneous restarts (fullRebuild, watchdog).
|
||||
let last_watcher_restart = 0;
|
||||
// The current watcher finished its initial scan; probes written before
|
||||
// that may not produce events.
|
||||
let watcher_ready = false;
|
||||
// A probe was written and the watcher hasn't reported it yet.
|
||||
let probe_pending = false;
|
||||
/**
|
||||
* Closes and re-creates the FS watcher. Deduped: callers that restart the
|
||||
* watcher around the same time (fullRebuild, reloadWatcher, watchdog) only
|
||||
@@ -48,11 +56,27 @@ function grabPagesSnapshot() {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* True when the watcher was last (re)created long enough ago that the
|
||||
* watchdog should consider re-creating it again.
|
||||
* Called periodically by the dev server. Re-creates the watcher only when it
|
||||
* failed to report the probe written on the previous tick, i.e. when it went
|
||||
* stale. A healthy watcher is left alone: re-creating it rescans the whole
|
||||
* project tree.
|
||||
*/
|
||||
export function watcherRestartAvailable() {
|
||||
return Date.now() - last_watcher_restart > 6000;
|
||||
export function watcherWatchdogTick() {
|
||||
if (!global.BUNEXT_PAGES_SRC_WATCHER || !watcher_ready)
|
||||
return;
|
||||
if (probe_pending) {
|
||||
restartWatcher();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mkdirSync(WATCH_PROBE_DIR, { recursive: true });
|
||||
writeFileSync(WATCH_PROBE_FILE, String(Date.now()));
|
||||
probe_pending = true;
|
||||
}
|
||||
catch (error) { }
|
||||
}
|
||||
function isWatchProbePath(p) {
|
||||
return (p === WATCH_PROBE_FILE || p === WATCH_PROBE_DIR || p === BUNX_CWD_DIR);
|
||||
}
|
||||
const TARGET_FILES_MATCH = /\.(tsx?|jsx?|css)$/;
|
||||
// Rebuild coalescing: editors emit bursts of events per save (change +
|
||||
@@ -170,10 +194,10 @@ function isPathExcluded(filename, full_file_path) {
|
||||
// install watches (and deliver events) for trees the bundler never reads.
|
||||
function buildIgnoredPatterns() {
|
||||
const ignored = [
|
||||
/(^|[\/\\])\../,
|
||||
(p) => !isWatchProbePath(p) && /(^|[\/\\])\../.test(p),
|
||||
/(^|[\/\\])node_modules([\/\\]|$)/,
|
||||
/(^|[\/\\])public([\/\\]|$)/,
|
||||
/(^|[\/\\])\.bunext([\/\\]|$)/,
|
||||
(p) => !isWatchProbePath(p) && /(^|[\/\\])\.bunext([\/\\]|$)/.test(p),
|
||||
/(^|[\/\\])\.git([\/\\]|$)/,
|
||||
/(^|[\/\\])dist([\/\\]|$)/,
|
||||
/bun\.lockb$/,
|
||||
@@ -195,6 +219,12 @@ function buildIgnoredPatterns() {
|
||||
return ignored;
|
||||
}
|
||||
export default async function watcherEsbuildCTX(params) {
|
||||
try {
|
||||
mkdirSync(WATCH_PROBE_DIR, { recursive: true });
|
||||
}
|
||||
catch (error) { }
|
||||
watcher_ready = false;
|
||||
probe_pending = false;
|
||||
const pages_src_watcher = chokidar.watch(ROOT_DIR, {
|
||||
ignored: buildIgnoredPatterns(),
|
||||
persistent: true,
|
||||
@@ -202,18 +232,27 @@ export default async function watcherEsbuildCTX(params) {
|
||||
});
|
||||
watcherHandlers(pages_src_watcher, (event, filePath) => handleEvent(event, filePath));
|
||||
const pages_snapshot = params?.pages_snapshot;
|
||||
if (pages_snapshot !== undefined) {
|
||||
pages_src_watcher.on("ready", () => {
|
||||
const current = grabPagesSnapshot();
|
||||
if (current !== undefined && current !== pages_snapshot) {
|
||||
pending_structural = true;
|
||||
scheduleRebuild();
|
||||
}
|
||||
});
|
||||
}
|
||||
pages_src_watcher.on("ready", () => {
|
||||
if (global.BUNEXT_PAGES_SRC_WATCHER === pages_src_watcher) {
|
||||
watcher_ready = true;
|
||||
}
|
||||
if (pages_snapshot === undefined)
|
||||
return;
|
||||
const current = grabPagesSnapshot();
|
||||
if (current !== undefined && current !== pages_snapshot) {
|
||||
pending_structural = true;
|
||||
scheduleRebuild();
|
||||
}
|
||||
});
|
||||
global.BUNEXT_PAGES_SRC_WATCHER = pages_src_watcher;
|
||||
}
|
||||
function handleEvent(event, filePath) {
|
||||
if (isWatchProbePath(filePath)) {
|
||||
if (filePath === WATCH_PROBE_FILE) {
|
||||
probe_pending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const filename = path.relative(ROOT_DIR, filePath);
|
||||
const full_file_path = filePath;
|
||||
|
||||
Reference in New Issue
Block a user