maxpool 1.5.2 → 1.5.3
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/account-manager.js +29 -2
- package/src/config.js +4 -0
- package/src/oauth.js +7 -0
- package/src/server.js +4 -1
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -48,6 +48,10 @@ const DEFAULT_SCHEDULER = {
|
|
|
48
48
|
safetyMaxGlobalActive: 150,
|
|
49
49
|
cooldownMs: 30_000,
|
|
50
50
|
maxCooldownMs: 15 * 60_000,
|
|
51
|
+
// Fixed cooldown for NETWORK-class failures (lost connectivity / token-refresh
|
|
52
|
+
// fetch-failed). Short + non-escalating so the fleet auto-recovers seconds after
|
|
53
|
+
// connectivity returns, instead of the exponential maxCooldownMs bench.
|
|
54
|
+
networkCooldownMs: 5_000,
|
|
51
55
|
weeklySoftThreshold: 0.65,
|
|
52
56
|
weeklyReserveThreshold: 0.85,
|
|
53
57
|
weeklyCriticalThreshold: 0.95,
|
|
@@ -1698,9 +1702,28 @@ export class AccountManager {
|
|
|
1698
1702
|
console.log(`[Maxpool] Account "${account.name}" disabled after HTTP ${status} (${reason})`);
|
|
1699
1703
|
}
|
|
1700
1704
|
|
|
1701
|
-
markTransientFailure(accountIndex, reason = 'transient_error') {
|
|
1705
|
+
markTransientFailure(accountIndex, reason = 'transient_error', { network = false } = {}) {
|
|
1702
1706
|
const account = this.accounts[accountIndex];
|
|
1703
1707
|
if (!account) return;
|
|
1708
|
+
|
|
1709
|
+
if (network) {
|
|
1710
|
+
// A network-class failure (lost connectivity / token-refresh `fetch failed`)
|
|
1711
|
+
// is a FLEET-WIDE condition, not this account's fault — every account fails at
|
|
1712
|
+
// once. Use a SHORT FIXED cooldown so the whole fleet retries within seconds of
|
|
1713
|
+
// connectivity returning and recovers AUTOMATICALLY, never the exponential
|
|
1714
|
+
// 15-min bench that stranded the fleet long after a multi-hour outage and forced
|
|
1715
|
+
// a manual restart (2026-06-29 hotel-network nightly cutoff). Deliberately does
|
|
1716
|
+
// NOT bump consecutiveFailures: a network blip must not poison the scoring
|
|
1717
|
+
// penalty (_scoreAccount) or prime the next REAL per-account failure for the max
|
|
1718
|
+
// cooldown — so the counter stays a pure request-health signal and needs no reset.
|
|
1719
|
+
account.failedRequests++;
|
|
1720
|
+
account.lastError = reason;
|
|
1721
|
+
account.lastErrorAt = Date.now();
|
|
1722
|
+
account.cooldownUntil = Date.now() + this.scheduler.networkCooldownMs;
|
|
1723
|
+
console.log(`[Maxpool] Account "${account.name}" cooling down for ${Math.ceil(this.scheduler.networkCooldownMs / 1000)}s after ${reason} (network — short fixed, auto-recovers)`);
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1704
1727
|
const failures = Math.max(1, account.consecutiveFailures + 1);
|
|
1705
1728
|
const cooldown = Math.min(
|
|
1706
1729
|
this.scheduler.maxCooldownMs,
|
|
@@ -1812,7 +1835,11 @@ export class AccountManager {
|
|
|
1812
1835
|
// a failed proactive refresh shouldn't kill a still-valid token
|
|
1813
1836
|
if (!account.expiresAt || Date.now() >= account.expiresAt) {
|
|
1814
1837
|
if (err.retryable) {
|
|
1815
|
-
|
|
1838
|
+
// A retryable refresh failure with NO HTTP status is a network/connectivity
|
|
1839
|
+
// failure (fetch failed / timeout) → short fixed cooldown so it auto-recovers
|
|
1840
|
+
// when the network returns. A retryable HTTP status (429 / 5xx from the OAuth
|
|
1841
|
+
// endpoint) is server-side → keep the exponential backoff.
|
|
1842
|
+
this.markTransientFailure(accountIndex, `token_refresh_${err.status || 'network'}`, { network: !err.status });
|
|
1816
1843
|
} else {
|
|
1817
1844
|
this.markAuthFailed(accountIndex, err.status || 401, 'token_refresh_failed');
|
|
1818
1845
|
}
|
package/src/config.js
CHANGED
|
@@ -59,6 +59,10 @@ export function createDefaultConfig() {
|
|
|
59
59
|
safetyMaxGlobalActive: 150,
|
|
60
60
|
cooldownMs: 30_000,
|
|
61
61
|
maxCooldownMs: 15 * 60_000,
|
|
62
|
+
// Fixed cooldown for network-class failures (lost connectivity / token-refresh
|
|
63
|
+
// fetch-failed) — short + non-escalating so the fleet auto-recovers seconds after
|
|
64
|
+
// connectivity returns, never the exponential maxCooldownMs bench.
|
|
65
|
+
networkCooldownMs: 5_000,
|
|
62
66
|
// Weekly (7d) quota tiers — how aggressively to de-prioritise an account
|
|
63
67
|
// as its weekly usage climbs. Each is a fraction (0..1) of the weekly
|
|
64
68
|
// limit. Below soft = full speed; soft..reserve = mild penalty;
|
package/src/oauth.js
CHANGED
|
@@ -17,6 +17,11 @@ const DEFAULT_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
|
17
17
|
export async function refreshAccessToken(refreshToken, endpoint = DEFAULT_TOKEN_ENDPOINT) {
|
|
18
18
|
const maxRetries = 2;
|
|
19
19
|
const baseDelayMs = 500;
|
|
20
|
+
// Bound each refresh POST so a hung connect during an outage can't pin the
|
|
21
|
+
// single-flight _refreshPromise indefinitely (which would stall that account's
|
|
22
|
+
// recovery). Comfortably above a normal refresh latency; a timeout is classified
|
|
23
|
+
// as a network error below and retried / short-cooled.
|
|
24
|
+
const perAttemptTimeoutMs = 10_000;
|
|
20
25
|
|
|
21
26
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
22
27
|
try {
|
|
@@ -37,6 +42,7 @@ export async function refreshAccessToken(refreshToken, endpoint = DEFAULT_TOKEN_
|
|
|
37
42
|
refresh_token: refreshToken,
|
|
38
43
|
client_id: DEFAULT_CLIENT_ID,
|
|
39
44
|
}),
|
|
45
|
+
signal: AbortSignal.timeout(perAttemptTimeoutMs),
|
|
40
46
|
});
|
|
41
47
|
|
|
42
48
|
if (!res.ok) {
|
|
@@ -60,6 +66,7 @@ export async function refreshAccessToken(refreshToken, endpoint = DEFAULT_TOKEN_
|
|
|
60
66
|
} catch (err) {
|
|
61
67
|
const isNetworkError = err instanceof Error &&
|
|
62
68
|
(err.message.includes('fetch failed') ||
|
|
69
|
+
err.name === 'TimeoutError' || err.name === 'AbortError' ||
|
|
63
70
|
(err.code === 'ECONNRESET' || err.code === 'ECONNREFUSED' ||
|
|
64
71
|
err.code === 'ETIMEDOUT' || err.code === 'UND_ERR_CONNECT_TIMEOUT'));
|
|
65
72
|
|
package/src/server.js
CHANGED
|
@@ -855,7 +855,10 @@ async function forwardRequest(
|
|
|
855
855
|
err.message.includes('terminated'));
|
|
856
856
|
|
|
857
857
|
if (isTransient) {
|
|
858
|
-
|
|
858
|
+
// Network-class (ECONNRESET / fetch failed / timeout / terminated): short fixed
|
|
859
|
+
// cooldown, no exponential escalation — the fleet auto-recovers seconds after
|
|
860
|
+
// connectivity returns instead of being benched for up to 15 min.
|
|
861
|
+
accountManager.markTransientFailure(account.index, err.code || err.message || 'network_error', { network: true });
|
|
859
862
|
accountManager.releaseAccount(lease);
|
|
860
863
|
excludedIndexes.add(account.index);
|
|
861
864
|
if (canRetryBufferedBody && retryCount + 1 < maxAttempts && !res.headersSent) {
|