perf(startup): skip inactive background services
This commit is contained in:
parent
7dfb346667
commit
27b37705b3
8 changed files with 107 additions and 16 deletions
|
|
@ -2,7 +2,6 @@ import { NextResponse } from "next/server";
|
||||||
import { getSettings, updateSettings } from "@/lib/localDb";
|
import { getSettings, updateSettings } from "@/lib/localDb";
|
||||||
import { applyOutboundProxyEnv } from "@/lib/network/outboundProxy";
|
import { applyOutboundProxyEnv } from "@/lib/network/outboundProxy";
|
||||||
import { resetComboRotation } from "open-sse/services/combo.js";
|
import { resetComboRotation } from "open-sse/services/combo.js";
|
||||||
import { runQuotaAutoPingTick } from "@/shared/services/quotaAutoPing";
|
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
@ -101,10 +100,12 @@ export async function PATCH(request) {
|
||||||
Object.prototype.hasOwnProperty.call(body, "claudeAutoPing") ||
|
Object.prototype.hasOwnProperty.call(body, "claudeAutoPing") ||
|
||||||
Object.prototype.hasOwnProperty.call(body, "codexAutoPing")
|
Object.prototype.hasOwnProperty.call(body, "codexAutoPing")
|
||||||
) {
|
) {
|
||||||
// Run once immediately after opt-in changes so users don't wait for the next scheduler tick.
|
// Keep the scheduler absent when no account opted in; load its provider graph only on demand.
|
||||||
runQuotaAutoPingTick().catch((error) => {
|
import("@/shared/services/quotaAutoPing")
|
||||||
console.warn("[AutoPing] settings-triggered tick failed:", error.message);
|
.then(({ configureQuotaAutoPing }) => {
|
||||||
});
|
configureQuotaAutoPing(settings);
|
||||||
|
})
|
||||||
|
.catch((error) => console.warn("[AutoPing] settings update failed:", error.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { password, oidcClientSecret, ...safeSettings } = settings;
|
const { password, oidcClientSecret, ...safeSettings } = settings;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { disableTunnel } from "@/lib/tunnel";
|
import { disableTunnel } from "@/lib/tunnel";
|
||||||
|
import { getSettings } from "@/lib/localDb";
|
||||||
|
import { configureTunnelMonitoring } from "@/shared/services/initializeApp";
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
try {
|
||||||
const result = await disableTunnel();
|
const result = await disableTunnel();
|
||||||
|
getSettings()
|
||||||
|
.then(configureTunnelMonitoring)
|
||||||
|
.catch((error) => console.warn("Tunnel monitor update failed:", error.message));
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Tunnel disable error:", error);
|
console.error("Tunnel disable error:", error);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { enableTunnel } from "@/lib/tunnel";
|
import { enableTunnel } from "@/lib/tunnel";
|
||||||
|
import { getSettings } from "@/lib/localDb";
|
||||||
|
import { configureTunnelMonitoring } from "@/shared/services/initializeApp";
|
||||||
|
|
||||||
const DNS_WARMUP_DELAY_MS = 8000;
|
const DNS_WARMUP_DELAY_MS = 8000;
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
try {
|
||||||
const result = await enableTunnel();
|
const result = await enableTunnel();
|
||||||
|
getSettings()
|
||||||
|
.then(configureTunnelMonitoring)
|
||||||
|
.catch((error) => console.warn("Tunnel monitor start failed:", error.message));
|
||||||
// Wait for DNS warmup to propagate at Cloudflare edge after tunnel registered
|
// Wait for DNS warmup to propagate at Cloudflare edge after tunnel registered
|
||||||
await new Promise((r) => setTimeout(r, DNS_WARMUP_DELAY_MS));
|
await new Promise((r) => setTimeout(r, DNS_WARMUP_DELAY_MS));
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { disableTailscale } from "@/lib/tunnel";
|
import { disableTailscale } from "@/lib/tunnel";
|
||||||
|
import { getSettings } from "@/lib/localDb";
|
||||||
|
import { configureTunnelMonitoring } from "@/shared/services/initializeApp";
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
try {
|
||||||
const result = await disableTailscale();
|
const result = await disableTailscale();
|
||||||
|
getSettings()
|
||||||
|
.then(configureTunnelMonitoring)
|
||||||
|
.catch((error) => console.warn("Tailscale monitor update failed:", error.message));
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Tailscale disable error:", error);
|
console.error("Tailscale disable error:", error);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { enableTailscale } from "@/lib/tunnel";
|
import { enableTailscale } from "@/lib/tunnel";
|
||||||
|
import { getSettings } from "@/lib/localDb";
|
||||||
|
import { configureTunnelMonitoring } from "@/shared/services/initializeApp";
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
try {
|
||||||
const result = await enableTailscale();
|
const result = await enableTailscale();
|
||||||
|
getSettings()
|
||||||
|
.then(configureTunnelMonitoring)
|
||||||
|
.catch((error) => console.warn("Tailscale monitor start failed:", error.message));
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Tailscale enable error:", error.message);
|
console.error("Tailscale enable error:", error.message);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import {
|
||||||
WATCHDOG_INTERVAL_MS, NETWORK_CHECK_INTERVAL_MS, VIRTUAL_IFACE_REGEX,
|
WATCHDOG_INTERVAL_MS, NETWORK_CHECK_INTERVAL_MS, VIRTUAL_IFACE_REGEX,
|
||||||
} from "@/lib/tunnel";
|
} from "@/lib/tunnel";
|
||||||
import { getMitmStatus, startMitm, loadEncryptedPassword, initDbHooks, restoreToolDNS, removeAllDNSEntriesSync } from "@/mitm/manager";
|
import { getMitmStatus, startMitm, loadEncryptedPassword, initDbHooks, restoreToolDNS, removeAllDNSEntriesSync } from "@/mitm/manager";
|
||||||
import { startQuotaAutoPing } from "@/shared/services/quotaAutoPing";
|
|
||||||
import { syncToJson as syncMitmAliasCache } from "@/lib/mitmAliasCache";
|
import { syncToJson as syncMitmAliasCache } from "@/lib/mitmAliasCache";
|
||||||
import { killAllBridges } from "@/lib/mcp/stdioSseBridge";
|
import { killAllBridges } from "@/lib/mcp/stdioSseBridge";
|
||||||
|
|
||||||
|
|
@ -98,22 +97,32 @@ async function runHeavyStartup() {
|
||||||
safeRestartTailscale("startup").catch((e) => console.log("[InitApp] Tailscale resume failed:", e.message));
|
safeRestartTailscale("startup").catch((e) => console.log("[InitApp] Tailscale resume failed:", e.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureCloudflared().catch(() => {});
|
if (settings.tunnelEnabled) ensureCloudflared().catch(() => {});
|
||||||
|
|
||||||
// Sync mitmAlias DB → JSON cache so standalone MITM server can read it
|
if (settings.mitmEnabled) {
|
||||||
syncMitmAliasCache().catch(() => {});
|
// Sync mitmAlias DB → JSON cache so standalone MITM server can read it.
|
||||||
|
syncMitmAliasCache().catch(() => {});
|
||||||
|
autoStartMitm(settings);
|
||||||
|
}
|
||||||
|
|
||||||
startWatchdog();
|
configureTunnelMonitoring(settings);
|
||||||
startNetworkMonitor();
|
|
||||||
autoStartMitm();
|
if (hasQuotaAutoPingEnabled(settings)) {
|
||||||
startQuotaAutoPing();
|
import("@/shared/services/quotaAutoPing")
|
||||||
|
.then(({ startQuotaAutoPing }) => startQuotaAutoPing())
|
||||||
|
.catch((e) => console.log("[AutoPing] scheduler start failed:", e.message));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoStartMitm() {
|
function hasQuotaAutoPingEnabled(settings) {
|
||||||
|
return [settings?.claudeAutoPing, settings?.codexAutoPing]
|
||||||
|
.some((config) => Object.values(config?.connections || {}).some(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoStartMitm(settings) {
|
||||||
if (g.mitmStartInProgress) return;
|
if (g.mitmStartInProgress) return;
|
||||||
g.mitmStartInProgress = true;
|
g.mitmStartInProgress = true;
|
||||||
try {
|
try {
|
||||||
const settings = await getSettings();
|
|
||||||
if (!settings.mitmEnabled) return;
|
if (!settings.mitmEnabled) return;
|
||||||
const mitmStatus = await getMitmStatus();
|
const mitmStatus = await getMitmStatus();
|
||||||
if (mitmStatus.running) return;
|
if (mitmStatus.running) return;
|
||||||
|
|
@ -232,6 +241,12 @@ function startWatchdog() {
|
||||||
if (g.watchdogInterval.unref) g.watchdogInterval.unref();
|
if (g.watchdogInterval.unref) g.watchdogInterval.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stopWatchdog() {
|
||||||
|
if (!g.watchdogInterval) return;
|
||||||
|
clearInterval(g.watchdogInterval);
|
||||||
|
g.watchdogInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Network monitor: detect IPv4 fingerprint change + sleep/wake ────────────
|
// ─── Network monitor: detect IPv4 fingerprint change + sleep/wake ────────────
|
||||||
|
|
||||||
function getNetworkFingerprint() {
|
function getNetworkFingerprint() {
|
||||||
|
|
@ -293,4 +308,23 @@ function startNetworkMonitor() {
|
||||||
if (g.networkMonitorInterval.unref) g.networkMonitorInterval.unref();
|
if (g.networkMonitorInterval.unref) g.networkMonitorInterval.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function stopNetworkMonitor() {
|
||||||
|
if (!g.networkMonitorInterval) return;
|
||||||
|
clearInterval(g.networkMonitorInterval);
|
||||||
|
g.networkMonitorInterval = null;
|
||||||
|
g.lastNetworkFingerprint = null;
|
||||||
|
g.lastOnline = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureTunnelMonitoring(settings) {
|
||||||
|
if (settings?.tunnelEnabled || settings?.tailscaleEnabled) {
|
||||||
|
startWatchdog();
|
||||||
|
startNetworkMonitor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopWatchdog();
|
||||||
|
stopNetworkMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
export default initializeApp;
|
export default initializeApp;
|
||||||
|
|
|
||||||
|
|
@ -296,3 +296,18 @@ export function startQuotaAutoPing() {
|
||||||
g.interval = setInterval(() => { runQuotaAutoPingTick().catch(() => {}); }, C.tickIntervalMs);
|
g.interval = setInterval(() => { runQuotaAutoPingTick().catch(() => {}); }, C.tickIntervalMs);
|
||||||
if (g.interval.unref) g.interval.unref();
|
if (g.interval.unref) g.interval.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stopQuotaAutoPing() {
|
||||||
|
if (!g.interval) return;
|
||||||
|
clearInterval(g.interval);
|
||||||
|
g.interval = null;
|
||||||
|
console.log("[AutoPing] scheduler stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureQuotaAutoPing(settings) {
|
||||||
|
const enabled = Object.values(C.providers).some((providerConfig) =>
|
||||||
|
Object.values(settings?.[providerConfig.settingsKey]?.connections || {}).some(Boolean)
|
||||||
|
);
|
||||||
|
if (enabled) startQuotaAutoPing();
|
||||||
|
else stopQuotaAutoPing();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ vi.mock("open-sse/executors/index.js", () => ({
|
||||||
|
|
||||||
describe("quota auto-ping", () => {
|
describe("quota auto-ping", () => {
|
||||||
let runQuotaAutoPingTick;
|
let runQuotaAutoPingTick;
|
||||||
|
let configureQuotaAutoPing;
|
||||||
let deps;
|
let deps;
|
||||||
let state;
|
let state;
|
||||||
let getCodexUsage;
|
let getCodexUsage;
|
||||||
|
|
@ -83,11 +84,12 @@ describe("quota auto-ping", () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
delete global.__quotaAutoPing;
|
||||||
|
|
||||||
({ getCodexUsage } = await import("open-sse/services/usage/codex.js"));
|
({ getCodexUsage } = await import("open-sse/services/usage/codex.js"));
|
||||||
({ getClaudeUsage } = await import("open-sse/services/usage/claude.js"));
|
({ getClaudeUsage } = await import("open-sse/services/usage/claude.js"));
|
||||||
({ getExecutor } = await import("open-sse/executors/index.js"));
|
({ getExecutor } = await import("open-sse/executors/index.js"));
|
||||||
({ runQuotaAutoPingTick } = await import("../../src/shared/services/quotaAutoPing.js"));
|
({ runQuotaAutoPingTick, configureQuotaAutoPing } = await import("../../src/shared/services/quotaAutoPing.js"));
|
||||||
|
|
||||||
deps = {
|
deps = {
|
||||||
getSettings: vi.fn(),
|
getSettings: vi.fn(),
|
||||||
|
|
@ -117,6 +119,25 @@ describe("quota auto-ping", () => {
|
||||||
expect(deps.proxyAwareFetch).not.toHaveBeenCalled();
|
expect(deps.proxyAwareFetch).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("starts the scheduler only when an account opts in", () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
configureQuotaAutoPing({ codexAutoPing: { connections: {} } });
|
||||||
|
expect(vi.getTimerCount()).toBe(0);
|
||||||
|
|
||||||
|
configureQuotaAutoPing({ codexAutoPing: { connections: { "codex-1": true } } });
|
||||||
|
expect(vi.getTimerCount()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops the scheduler when the last account opts out", () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
configureQuotaAutoPing({ claudeAutoPing: { connections: { "claude-1": true } } });
|
||||||
|
|
||||||
|
configureQuotaAutoPing({ claudeAutoPing: { connections: { "claude-1": false } } });
|
||||||
|
|
||||||
|
expect(vi.getTimerCount()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not ping Codex on the first resetAt observation", async () => {
|
it("does not ping Codex on the first resetAt observation", async () => {
|
||||||
deps.getSettings.mockResolvedValue({ codexAutoPing: { connections: { "codex-1": true } } });
|
deps.getSettings.mockResolvedValue({ codexAutoPing: { connections: { "codex-1": true } } });
|
||||||
deps.getProviderConnections.mockImplementation(async ({ provider }) => (
|
deps.getProviderConnections.mockImplementation(async ({ provider }) => (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue