muaddib-scanner 2.11.57 → 2.11.58
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
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
const { NPM_PACKAGE_REGEX } = require('../shared/constants.js');
|
|
2
2
|
const { debugLog } = require('../utils.js');
|
|
3
|
-
const { acquireRegistrySlot, releaseRegistrySlot } = require('../shared/http-limiter.js');
|
|
3
|
+
const { acquireRegistrySlot, releaseRegistrySlot, signal429 } = require('../shared/http-limiter.js');
|
|
4
4
|
const { computeAdvancedRegistrySignals } = require('../integrations/registry-signals.js');
|
|
5
5
|
|
|
6
6
|
const REGISTRY_URL = 'https://registry.npmjs.org';
|
|
7
7
|
const DOWNLOADS_URL = 'https://api.npmjs.org/downloads/point/last-week';
|
|
8
8
|
const SEARCH_URL = 'https://registry.npmjs.org/-/v1/search';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Env-tunable; defaults preserve prior behavior except MAX_RETRIES (3 → 5) for more headroom
|
|
11
|
+
// under sustained 429s during a large evaluate burst.
|
|
12
|
+
const REQUEST_TIMEOUT = Math.max(1000, parseInt(process.env.MUADDIB_REGISTRY_TIMEOUT_MS, 10) || 10000); // 10s default
|
|
13
|
+
const MAX_RETRIES = Math.max(1, parseInt(process.env.MUADDIB_REGISTRY_RETRIES, 10) || 5);
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Create a timeout signal, with fallback for older Node versions.
|
|
@@ -31,10 +33,12 @@ async function fetchWithRetry(url) {
|
|
|
31
33
|
response = await fetch(url, { signal });
|
|
32
34
|
} catch {
|
|
33
35
|
cleanup();
|
|
34
|
-
// REG-001: Retry on timeout/abort instead of returning null immediately
|
|
36
|
+
// REG-001: Retry on timeout/abort instead of returning null immediately.
|
|
37
|
+
// Jittered exponential backoff avoids synchronized retry storms across the
|
|
38
|
+
// (up to MUADDIB_REGISTRY_CONCURRENCY) concurrent fetches.
|
|
35
39
|
if (attempt < MAX_RETRIES - 1) {
|
|
36
40
|
const backoff = Math.min(1000 * Math.pow(2, attempt), 8000);
|
|
37
|
-
await new Promise(r => setTimeout(r, backoff));
|
|
41
|
+
await new Promise(r => setTimeout(r, Math.round(backoff * (0.5 + Math.random() * 0.5))));
|
|
38
42
|
}
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
@@ -48,12 +52,16 @@ async function fetchWithRetry(url) {
|
|
|
48
52
|
return null;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
// 429 = rate limit
|
|
55
|
+
// 429 = rate limit. Drain the SHARED token bucket so EVERY in-flight request
|
|
56
|
+
// (not just this one) backs off together — fixes the thundering-herd 429 storm
|
|
57
|
+
// that left ~17% of packages metadata-less in a local evaluate run. Then honor
|
|
58
|
+
// Retry-After (capped at 30s) with jitter so retries don't re-synchronize.
|
|
52
59
|
if (response.status === 429) {
|
|
53
60
|
try { await response.text(); } catch (e) { debugLog('response drain failed:', e.message); }
|
|
61
|
+
try { signal429(); } catch { /* limiter is best-effort */ }
|
|
54
62
|
const retryAfter = parseInt(response.headers.get('retry-after'), 10);
|
|
55
|
-
const
|
|
56
|
-
await new Promise(r => setTimeout(r,
|
|
63
|
+
const base = Math.min(retryAfter && retryAfter > 0 ? retryAfter * 1000 : 2000, 30000);
|
|
64
|
+
await new Promise(r => setTimeout(r, Math.round(base * (0.5 + Math.random() * 0.5))));
|
|
57
65
|
continue;
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Centralized HTTP concurrency + rate limiter for npm registry requests.
|
|
5
5
|
*
|
|
6
6
|
* Two layers of protection:
|
|
7
|
-
* 1. Concurrency semaphore (REGISTRY_SEMAPHORE_MAX
|
|
8
|
-
* 2. Rate limiter (RATE_LIMIT_PER_SEC
|
|
7
|
+
* 1. Concurrency semaphore (REGISTRY_SEMAPHORE_MAX, default 20, env MUADDIB_REGISTRY_CONCURRENCY) — caps in-flight requests
|
|
8
|
+
* 2. Rate limiter (RATE_LIMIT_PER_SEC, default 30, env MUADDIB_REGISTRY_RATE) — caps requests/second via token bucket
|
|
9
9
|
*
|
|
10
10
|
* Without rate limiting, 10 concurrent slots × fast-completing requests = 100+ req/s
|
|
11
11
|
* bursts that trigger npm 429 responses → exponential backoff → scan times 10s→90s.
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
* NOT covered: api.npmjs.org (different server), replicate.npmjs.com (CouchDB changes stream).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// Env-tunable so a constrained client (e.g. local/Windows `evaluate` runs that hit npm 429s during
|
|
18
|
+
// the ~1644-request metadata burst over 548 packages) can dial the burst down without code edits.
|
|
19
|
+
// Defaults preserve prior behavior (20 in-flight / 30 req/s).
|
|
20
|
+
const REGISTRY_SEMAPHORE_MAX = Math.max(1, parseInt(process.env.MUADDIB_REGISTRY_CONCURRENCY, 10) || 20);
|
|
21
|
+
const RATE_LIMIT_PER_SEC = Math.max(1, parseInt(process.env.MUADDIB_REGISTRY_RATE, 10) || 30);
|
|
19
22
|
|
|
20
23
|
// --- Concurrency semaphore ---
|
|
21
24
|
|