Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 2151x 2151x 2151x 2151x 2151x 2151x 9x 9x 9x 9x 9x 9x 54x 54x 54x 5x 5x 54x 54x 9x 5743x 5743x 5743x 9x 898x 898x 898x 898x 898x 155x 155x 376x 376x 155x 898x 898x 9x 9x 9x 9x 9x 9x 448x 448x 448x 1x 1x 443x 443x 443x 448x 448x 448x 448x 448x 448x 448x 9x 9x 9x 452x 452x 452x 1x 1x 449x 449x 449x 449x 452x 452x 452x 452x 452x | // RetryPolicy (DESIGN 2.3) and ReconnectPolicy (DESIGN 2.5) with the exact backoff formula.
import { localError } from './errors.js';
export { parseRetryAfter } from './retry-after.js';
export const DEFAULT_RETRY_POLICY = Object.freeze({
maxRetries: 2,
initialDelayMs: 200,
multiplier: 2.0,
maxDelayMs: 4000,
jitter: 0.3,
respectRetryAfter: true,
maxRetryAfterMs: 30000,
});
// 8 consecutive attempts wait 0.5, 1, 2, 4, 4, 4, 4, 4 s (about 23 s plus or minus 30 percent), so the
// defaults ride out a 10 s network drop (acceptance 6.4). The count restarts after a reconnect succeeds.
export const DEFAULT_RECONNECT_POLICY = Object.freeze({
enabled: true,
maxAttempts: 8,
initialDelayMs: 500,
multiplier: 2,
maxDelayMs: 4000,
jitter: 0.3,
});
/**
* delay(n) = min(maxDelayMs, initialDelayMs * multiplier^(n-1)) * (1 + U(-jitter, +jitter)), n >= 1.
* `random` returns a number in [0, 1); U(-j, +j) = (2 * random() - 1) * j. Result rounded to whole ms.
*/
export function computeBackoffDelay(n, policy, random) {
const p = policy || DEFAULT_RETRY_POLICY;
const rnd = typeof random === 'function' ? random : Math.random;
const base = Math.min(p.maxDelayMs, p.initialDelayMs * Math.pow(p.multiplier, Math.max(0, n - 1)));
const u = (rnd() * 2 - 1) * (p.jitter || 0);
return Math.max(0, Math.round(base * (1 + u)));
}
/**
* Backoff delay of retry n, raised to the server's Retry-After (capped at maxRetryAfterMs)
* when respectRetryAfter is on.
*/
export function computeRetryDelay(n, policy, random, retryAfterMs) {
const p = policy || DEFAULT_RETRY_POLICY;
let d = computeBackoffDelay(n, p, random);
if (p.respectRetryAfter && typeof retryAfterMs === 'number' && retryAfterMs >= 0) {
d = Math.max(d, Math.min(retryAfterMs, p.maxRetryAfterMs));
}
return d;
}
function check(cond, what) {
if (!cond) throw localError(90010, 'invalid policy field ' + what);
}
function merge(base, override) {
const out = {};
const keys = Object.keys(base);
for (let i = 0; i < keys.length; i++) out[keys[i]] = base[keys[i]];
if (override && typeof override === 'object') {
const ok = Object.keys(override);
for (let i = 0; i < ok.length; i++) {
if (override[ok[i]] !== undefined) out[ok[i]] = override[ok[i]];
}
}
return out;
}
const isNonNegInt = (v) => typeof v === 'number' && isFinite(v) && v >= 0 && Math.floor(v) === v;
const isNonNeg = (v) => typeof v === 'number' && isFinite(v) && v >= 0;
/** Merges a partial RetryPolicy over a base policy. `false` disables retries. */
export function normalizeRetryPolicy(override, base) {
const b = base || DEFAULT_RETRY_POLICY;
if (override === false) return Object.freeze(merge(b, { maxRetries: 0 }));
if (override !== undefined && override !== null && typeof override !== 'object') {
throw localError(90010, 'retry must be an object or false');
}
const p = merge(b, override);
check(isNonNegInt(p.maxRetries), 'maxRetries');
check(isNonNeg(p.initialDelayMs), 'initialDelayMs');
check(typeof p.multiplier === 'number' && isFinite(p.multiplier) && p.multiplier >= 1, 'multiplier');
check(isNonNeg(p.maxDelayMs), 'maxDelayMs');
check(typeof p.jitter === 'number' && p.jitter >= 0 && p.jitter <= 1, 'jitter');
check(typeof p.respectRetryAfter === 'boolean', 'respectRetryAfter');
check(isNonNeg(p.maxRetryAfterMs), 'maxRetryAfterMs');
return Object.freeze(p);
}
/** Merges a partial ReconnectPolicy over a base policy. `false` disables reconnects. */
export function normalizeReconnectPolicy(override, base) {
const b = base || DEFAULT_RECONNECT_POLICY;
if (override === false) return Object.freeze(merge(b, { enabled: false }));
if (override !== undefined && override !== null && typeof override !== 'object') {
throw localError(90010, 'reconnect must be an object or false');
}
const p = merge(b, override);
check(typeof p.enabled === 'boolean', 'enabled');
check(isNonNegInt(p.maxAttempts), 'maxAttempts');
check(isNonNeg(p.initialDelayMs), 'initialDelayMs');
check(typeof p.multiplier === 'number' && isFinite(p.multiplier) && p.multiplier >= 1, 'multiplier');
check(isNonNeg(p.maxDelayMs), 'maxDelayMs');
check(typeof p.jitter === 'number' && p.jitter >= 0 && p.jitter <= 1, 'jitter');
return Object.freeze(p);
}
|