Feat : Auto restart after crash
This commit is contained in:
@@ -246,23 +246,25 @@ export async function getRequestDetailById(id) {
|
||||
return db.data.records.find(r => r.id === id) || null;
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
let shutdownHandlerRegistered = false;
|
||||
// Graceful shutdown — use named handler so we can remove it on re-registration
|
||||
const _shutdownHandler = async () => {
|
||||
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
|
||||
if (writeBuffer.length > 0) await flushToDatabase();
|
||||
};
|
||||
|
||||
function ensureShutdownHandler() {
|
||||
if (shutdownHandlerRegistered || isCloud) return;
|
||||
if (isCloud) return;
|
||||
|
||||
const handler = async () => {
|
||||
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
|
||||
if (writeBuffer.length > 0) await flushToDatabase();
|
||||
};
|
||||
// Remove any previously registered listeners from this module (hot-reload safety)
|
||||
process.off("beforeExit", _shutdownHandler);
|
||||
process.off("SIGINT", _shutdownHandler);
|
||||
process.off("SIGTERM", _shutdownHandler);
|
||||
process.off("exit", _shutdownHandler);
|
||||
|
||||
process.on("beforeExit", handler);
|
||||
process.on("SIGINT", handler);
|
||||
process.on("SIGTERM", handler);
|
||||
process.on("exit", handler);
|
||||
|
||||
shutdownHandlerRegistered = true;
|
||||
process.on("beforeExit", _shutdownHandler);
|
||||
process.on("SIGINT", _shutdownHandler);
|
||||
process.on("SIGTERM", _shutdownHandler);
|
||||
process.on("exit", _shutdownHandler);
|
||||
}
|
||||
|
||||
ensureShutdownHandler();
|
||||
|
||||
@@ -141,8 +141,10 @@ export async function spawnCloudflared(tunnelToken) {
|
||||
|
||||
const handleLog = (data) => {
|
||||
const msg = data.toString();
|
||||
if (msg.includes("Registered tunnel connection")) {
|
||||
connectionCount++;
|
||||
// Count exact occurrences in this chunk (each chunk may contain multiple lines)
|
||||
const matches = msg.match(/Registered tunnel connection/g);
|
||||
if (matches) {
|
||||
connectionCount += matches.length;
|
||||
if (connectionCount >= 4 && !resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
@@ -165,6 +167,7 @@ export async function spawnCloudflared(tunnelToken) {
|
||||
child.on("exit", (code) => {
|
||||
cloudflaredProcess = null;
|
||||
clearPid();
|
||||
const wasConnected = resolved; // true = already connected successfully
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
@@ -173,8 +176,8 @@ export async function spawnCloudflared(tunnelToken) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify reconnect handler if tunnel died after successful connection
|
||||
if (unexpectedExitHandler) {
|
||||
// Only notify on unexpected exit AFTER successful connection
|
||||
if (wasConnected && unexpectedExitHandler) {
|
||||
unexpectedExitHandler();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ const MACHINE_ID_SALT = "9router-tunnel-salt";
|
||||
const API_KEY_SECRET = "9router-tunnel-api-key-secret";
|
||||
const SHORT_ID_LENGTH = 6;
|
||||
const SHORT_ID_CHARS = "abcdefghijklmnpqrstuvwxyz23456789";
|
||||
const RECONNECT_DELAYS_MS = [5000, 15000, 30000];
|
||||
const RECONNECT_DELAYS_MS = [5000, 10000, 20000, 30000, 60000];
|
||||
const MAX_RECONNECT_ATTEMPTS = RECONNECT_DELAYS_MS.length;
|
||||
|
||||
let isReconnecting = false;
|
||||
|
||||
@@ -83,8 +84,10 @@ export async function enableTunnel() {
|
||||
|
||||
await updateSettings({ tunnelEnabled: true, tunnelUrl: hostname });
|
||||
|
||||
// Register exit handler for auto-reconnect on unexpected crash/sleep-wake
|
||||
setUnexpectedExitHandler(() => scheduleReconnect(0));
|
||||
// Re-register exit handler each time tunnel starts (handles reconnect scenario too)
|
||||
setUnexpectedExitHandler(() => {
|
||||
if (!isReconnecting) scheduleReconnect(0);
|
||||
});
|
||||
|
||||
return { success: true, tunnelUrl: hostname, shortId };
|
||||
}
|
||||
@@ -112,7 +115,7 @@ async function scheduleReconnect(attempt) {
|
||||
console.log(`[Tunnel] Reconnect attempt ${attempt + 1} failed:`, err.message);
|
||||
isReconnecting = false;
|
||||
const nextAttempt = attempt + 1;
|
||||
if (nextAttempt < RECONNECT_DELAYS_MS.length) {
|
||||
if (nextAttempt < MAX_RECONNECT_ATTEMPTS) {
|
||||
scheduleReconnect(nextAttempt);
|
||||
} else {
|
||||
console.log("[Tunnel] All reconnect attempts exhausted");
|
||||
|
||||
@@ -245,8 +245,11 @@ export async function saveRequestUsage(entry) {
|
||||
entry.cost = entryCost;
|
||||
db.data.history.push(entry);
|
||||
|
||||
// Optional: Limit history size if needed in future
|
||||
// if (db.data.history.length > 10000) db.data.history.shift();
|
||||
// Cap history to prevent unbounded memory/disk growth
|
||||
const MAX_HISTORY = 10000;
|
||||
if (db.data.history.length > MAX_HISTORY) {
|
||||
db.data.history.splice(0, db.data.history.length - MAX_HISTORY);
|
||||
}
|
||||
|
||||
await db.write();
|
||||
statsEmitter.emit("update");
|
||||
|
||||
Reference in New Issue
Block a user