muaddib-scanner 2.11.109 → 2.11.110
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
CHANGED
|
@@ -98,7 +98,10 @@ function computeBackoffTransition(state, event, consts = {}) {
|
|
|
98
98
|
// Full-quiet reset (the incident is over, restart at base).
|
|
99
99
|
const quietResetMs = s.lastPauseMs * 2 + base * 5;
|
|
100
100
|
if (s.last429At && now - s.last429At > quietResetMs) s.level = 0;
|
|
101
|
-
s
|
|
101
|
+
// Cap: beyond ~12 both the pause (60s) and the rate (1/s floor) are
|
|
102
|
+
// saturated — an unbounded counter only makes operators read "level 25"
|
|
103
|
+
// as an emergency when it carries no additional behavior.
|
|
104
|
+
s.level = Math.min(s.level + 1, 12);
|
|
102
105
|
const pause = Math.min(max, base * 2 ** (s.level - 1));
|
|
103
106
|
s.lastPauseMs = pause;
|
|
104
107
|
s.last429At = now;
|
|
@@ -159,20 +162,37 @@ function hostForUrl(url) {
|
|
|
159
162
|
try { return new URL(url).hostname || DEFAULT_HOST; } catch { return DEFAULT_HOST; }
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
function _effectiveRate() {
|
|
165
|
+
function _effectiveRate(level = 0) {
|
|
166
|
+
let rate = RATE_LIMIT_PER_SEC;
|
|
163
167
|
if (BOOT_SLOWSTART_MS > 0 && Date.now() - _bootAt < BOOT_SLOWSTART_MS) {
|
|
164
|
-
|
|
168
|
+
rate = Math.max(1, Math.floor(rate / 4));
|
|
165
169
|
}
|
|
166
|
-
|
|
170
|
+
// Rate-by-level (the partial-throttle fix, 2026-06-12 evening): the pause
|
|
171
|
+
// alone cannot converge against a registry that PERMANENTLY rejects a
|
|
172
|
+
// fraction of requests — every post-pause burst guarantees a 429, every
|
|
173
|
+
// window stays dirty, the level ratchets to the cap and throughput pins at
|
|
174
|
+
// ~10 req/min forever (observed: level 25, zero de-escalations, while
|
|
175
|
+
// tarball downloads flowed fine). Halving the SEND RATE per level (floor
|
|
176
|
+
// 1 req/s) makes a clean 30s window reachable — 30 spaced probes instead of
|
|
177
|
+
// one burst — so the AIMD de-escalation actually fires and the brain
|
|
178
|
+
// CONVERGES on the registry's real granted budget instead of oscillating
|
|
179
|
+
// burst→reject at the cap.
|
|
180
|
+
if (level > 0) rate = Math.max(1, Math.floor(rate / 2 ** Math.min(level, 5)));
|
|
181
|
+
return rate;
|
|
167
182
|
}
|
|
168
183
|
|
|
169
184
|
function _refillTokens(b) {
|
|
170
185
|
const now = Date.now();
|
|
171
186
|
if (now < b.bo.pauseUntil) return; // backoff pause: no refills, no grants
|
|
172
|
-
const rate = _effectiveRate();
|
|
187
|
+
const rate = _effectiveRate(b.bo.level);
|
|
173
188
|
if (b.bo.pauseUntil > b.lastRefill) {
|
|
174
|
-
// First refill after a backoff pause:
|
|
175
|
-
|
|
189
|
+
// First refill after a backoff pause: PROBE OF ONE. The previous half-
|
|
190
|
+
// budget restart fired a 10-request burst the instant the pause expired —
|
|
191
|
+
// against a partially-throttling registry that burst GUARANTEED a 429 and
|
|
192
|
+
// re-armed the next pause. One spaced probe at a time is how the level's
|
|
193
|
+
// reduced rate (see _effectiveRate) gets a chance to produce the clean
|
|
194
|
+
// window that de-escalates.
|
|
195
|
+
b.tokens = 1;
|
|
176
196
|
b.lastRefill = now;
|
|
177
197
|
return;
|
|
178
198
|
}
|