diff --git a/index.mjs b/index.mjs index 8d87ce82..70708854 100644 --- a/index.mjs +++ b/index.mjs @@ -8,12 +8,12 @@ import express from "express"; import { createServer } from "http"; import compression from "compression"; import WebSocket from "ws"; +import { LRUCache } from "lru-cache"; import { baremuxPath } from "@mercuryworkshop/bare-mux/node"; import { epoxyPath } from "@mercuryworkshop/epoxy-transport"; import { libcurlPath } from "@mercuryworkshop/libcurl-transport"; import { uvPath } from "@titaniumnetwork-dev/ultraviolet"; import wisp from "wisp-server-node"; -import NodeCache from "node-cache"; const surgeConfigPath = path.resolve("surge.config.json"); const isSurgedRun = process.argv.includes("--surged"); @@ -37,7 +37,11 @@ function applySurgeAndRestartIfNeeded() { const config = JSON.parse(fs.readFileSync(surgeConfigPath, "utf-8")); const nodeArgs = config.nodeFlags.concat([path.resolve("index.mjs"), "--surged"]); - const env = { ...process.env, UV_THREADPOOL_SIZE: String(config.uvThreadpoolSize), ALREADY_SURGED: "true" }; + const env = { + ...process.env, + UV_THREADPOOL_SIZE: String(config.uvThreadpoolSize), + ALREADY_SURGED: "true" + }; console.log(`[~] Relaunching with Node flags: ${config.nodeFlags.join(' ')}`); const relaunch = spawnSync(process.execPath, nodeArgs, { stdio: 'inherit', env }); @@ -47,30 +51,38 @@ function applySurgeAndRestartIfNeeded() { applySurgeAndRestartIfNeeded(); if (global.gc) { - setInterval(() => global.gc(), 15000); + setInterval(() => { + const { heapUsed, heapTotal } = process.memoryUsage(); + if (heapTotal > 0 && heapUsed / heapTotal > 0.7) { + global.gc(); + console.info('[~] Performed GC due to high heap usage'); + } + }, 60_000); } import './others/scaler.mjs'; import './others/warmup.mjs'; -const cache = new NodeCache({ stdTTL: 345600, checkperiod: 3600, useClones: false }); -const port = parseInt(process.env.PORT || "3000", 10); -cluster.schedulingPolicy = cluster.SCHED_RR; +const cache = new LRUCache({ + max: 1000, + ttl: 4 * 24 * 60 * 60 * 1000, + allowStale: false, +}); -function logInfo(message) { console.info(`[~] ${message}`); } -function logSuccess(message) { console.info(`[+] ${message}`); } -function logError(error) { - const msg = error instanceof Error ? error.message : error; - console.error(`[!] ${msg}`); -} +const port = parseInt(process.env.PORT || "3000", 10); + +function logInfo(msg) { console.info(`[~] ${msg}`); } +function logSuccess(msg) { console.info(`[+] ${msg}`); } +function logError(err) { console.error(`[!] ${err instanceof Error ? err.message : err}`); } process.on("uncaughtException", err => logError(`Unhandled Exception: ${err}`)); process.on("unhandledRejection", reason => logError(`Unhandled Rejection: ${reason}`)); if (cluster.isPrimary) { - const numCPUs = os.cpus().length; - logInfo(`Master started. Forking ${numCPUs} workers...`); - for (let i = 0; i < numCPUs; i++) cluster.fork(); + const cpus = os.cpus().length; + const workers = Math.max(1, cpus - 1); + logInfo(`Master: forking ${workers} of ${cpus} cores`); + for (let i = 0; i < workers; i++) cluster.fork(); cluster.on("exit", (worker, code, signal) => { logError(`Worker ${worker.process.pid} exited (code=${code}, signal=${signal}). Restarting...`); @@ -79,9 +91,9 @@ if (cluster.isPrimary) { let current = 0; const server = net.createServer({ pauseOnConnect: true }, conn => { - const workers = Object.values(cluster.workers); - if (!workers.length) return conn.destroy(); - const worker = workers[current++ % workers.length]; + const workersArr = Object.values(cluster.workers); + if (!workersArr.length) return conn.destroy(); + const worker = workersArr[current++ % workersArr.length]; worker.send("sticky-session:connection", conn); }); @@ -93,14 +105,16 @@ if (cluster.isPrimary) { const publicPath = path.join(__dirname, "public"); const app = express(); - app.use(compression({ level: 9, threshold: 128, memLevel: 9 })); + app.use(compression({ level: 4, memLevel: 4, threshold: 1024 })); + app.use((req, res, next) => { const key = req.originalUrl; - if (cache.has(key)) { + const val = cache.get(key); + if (val) { res.setHeader("X-Cache", "HIT"); - return res.send(cache.get(key)); + return res.send(val); } - res.sendResponse = res.send; + res.sendResponse = res.send.bind(res); res.send = body => { cache.set(key, body); res.setHeader("X-Cache", "MISS"); @@ -117,6 +131,7 @@ if (cluster.isPrimary) { app.use("/wah/", express.static(uvPath, staticOpts)); app.use(express.json()); + const sendHtml = file => (req, res) => res.sendFile(path.join(publicPath, file)); app.get('/', sendHtml('$.html')); app.get('/g', sendHtml('!.html')); @@ -132,33 +147,35 @@ if (cluster.isPrimary) { pingWSS.on("connection", (ws, req) => { const remote = req.socket.remoteAddress || 'unknown'; let lat = []; - const pingInterval = setInterval(() => { + const interval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() })); }, 1000); - ws.on('message', msg => { try { const data = JSON.parse(msg); if (data.type === 'pong' && data.timestamp) { - const delta = Date.now() - data.timestamp; - lat.push(delta); + const d = Date.now() - data.timestamp; + lat.push(d); if (lat.length > 5) lat.shift(); - ws.send(JSON.stringify({ type: 'latency', latency: delta })); + ws.send(JSON.stringify({ type: 'latency', latency: d })); } - } catch (e) { logError(`Ping error: ${e}`); } + } catch(e) { logError(`Ping error: ${e}`); } }); - ws.on('close', () => { - clearInterval(pingInterval); + clearInterval(interval); const avg = lat.length ? (lat.reduce((a,b)=>a+b)/lat.length).toFixed(2) : 0; logInfo(`WS ${remote} closed. Avg latency ${avg}ms`); }); }); server.on('upgrade', (req, sock, head) => { - if (req.url === '/w/ping') pingWSS.handleUpgrade(req, sock, head, ws => pingWSS.emit('connection', ws, req)); - else if (req.url.startsWith('/w/')) wisp.routeRequest(req, sock, head); - else sock.end(); + if (req.url === '/w/ping') { + pingWSS.handleUpgrade(req, sock, head, ws => pingWSS.emit('connection', ws, req)); + } else if (req.url.startsWith('/w/')) { + wisp.routeRequest(req, sock, head); + } else { + sock.end(); + } }); server.on('error', err => logError(`Worker server error: ${err}`));