maxpool 1.5.52 → 1.5.53
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/package.json +1 -1
- package/src/server.js +15 -3
- package/src/tui.js +5 -1
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -256,7 +256,7 @@ export function createProxyServer(accountManager, config, hooks = {}) {
|
|
|
256
256
|
let requestInfo = {};
|
|
257
257
|
// Backstop reaper (defense-in-depth for any UNKNOWN future hang the specific
|
|
258
258
|
// TTFB/idle/body/drain guards don't cover). See startIdleRequestReaper.
|
|
259
|
-
const reaperTimer = startIdleRequestReaper(res, reqId, REQUEST_IDLE_MAX_MS);
|
|
259
|
+
const reaperTimer = startIdleRequestReaper(res, reqId, REQUEST_IDLE_MAX_MS, { getRequestInfo: () => requestInfo });
|
|
260
260
|
try {
|
|
261
261
|
// Buffer request body (needed for retry on 429)
|
|
262
262
|
const bodyChunks = [];
|
|
@@ -2407,12 +2407,24 @@ function mappedModel(originalModel, account) {
|
|
|
2407
2407
|
* (which has no idempotency guard). Clock/timer injectable for tests. Returns the
|
|
2408
2408
|
* interval handle (caller clearInterval()s it in the request's finally).
|
|
2409
2409
|
*/
|
|
2410
|
-
function startIdleRequestReaper(res, reqId, idleMs, { now = Date.now, setIntervalFn = setInterval } = {}) {
|
|
2410
|
+
function startIdleRequestReaper(res, reqId, idleMs, { now = Date.now, setIntervalFn = setInterval, getRequestInfo = null } = {}) {
|
|
2411
2411
|
let lastBytes = res.socket?.bytesWritten ?? 0;
|
|
2412
2412
|
let lastProgressAt = now();
|
|
2413
2413
|
const timer = setIntervalFn(() => {
|
|
2414
|
+
// Count only REAL progress. `bytesWritten` includes maxpool's OWN queue keepalive,
|
|
2415
|
+
// which pings every 10s — so a request stuck forever kept resetting this watchdog with
|
|
2416
|
+
// our own noise and could never be reaped. Observed 2026-07-29: 50 requests pinned
|
|
2417
|
+
// "in-flight" on one account for up to 6.7h, serving zero, which distorted the load
|
|
2418
|
+
// balancer into avoiding a healthy account. A held request is progressing only if the
|
|
2419
|
+
// UPSTREAM produced something; heartbeat bytes prove nothing.
|
|
2420
|
+
const heldOnHeartbeat = Boolean(getRequestInfo?.()?.queueHeartbeatActive);
|
|
2414
2421
|
const bytes = res.socket?.bytesWritten ?? lastBytes;
|
|
2415
|
-
if (bytes !== lastBytes) {
|
|
2422
|
+
if (bytes !== lastBytes) {
|
|
2423
|
+
lastBytes = bytes;
|
|
2424
|
+
if (!heldOnHeartbeat) { lastProgressAt = now(); return; }
|
|
2425
|
+
// Held: those bytes were OUR keepalive, so they are not progress — deliberately
|
|
2426
|
+
// fall through to the staleness check rather than returning.
|
|
2427
|
+
}
|
|
2416
2428
|
if (now() - lastProgressAt >= idleMs && !res.writableEnded && !res.destroyed) {
|
|
2417
2429
|
console.error(`[Maxpool] Request ${reqId} — no write progress for ${Math.round(idleMs / 1000)}s (backstop reaper); force-aborting a stuck request to free its account slot`);
|
|
2418
2430
|
res.destroy();
|
package/src/tui.js
CHANGED
|
@@ -1214,7 +1214,11 @@ export class TUI {
|
|
|
1214
1214
|
|
|
1215
1215
|
// Active requests
|
|
1216
1216
|
const now = Date.now();
|
|
1217
|
-
|
|
1217
|
+
// NEWEST FIRST, matching the completed log directly below (which is `unshift`ed).
|
|
1218
|
+
// A Map iterates in INSERTION order, so this block used to run oldest-first while the
|
|
1219
|
+
// log under it ran newest-first — the panel disagreed with itself and the newest thing
|
|
1220
|
+
// that happened was buried in the middle of the screen.
|
|
1221
|
+
for (const [, r] of [...this.active].reverse()) {
|
|
1218
1222
|
const el = ((now - r.started) / 1000).toFixed(1);
|
|
1219
1223
|
const sp = cyan(SPINNER[this.frame]);
|
|
1220
1224
|
const a = r.account ? ` → ${r.account}` : '';
|