mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(services): retry malformed capability probes after the existing backoff
Rebased onto current main with review follow-ups folded in. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
0265ad4c19
commit
a37a94fba1
5 files changed
+54
-8
No files matched your search
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
- Director timeline seeks cross the dateline along the short longitude arc.
|
- Director timeline seeks cross the dateline along the short longitude arc.
|
||||||
|
|
||||||
|
- Boundary capability checks recover after unsuccessful or malformed status responses, using the existing retry backoff.
|
||||||
|
|
||||||
- Treat explicitly current, complete empty vessel coverage as a successful update,
|
- Treat explicitly current, complete empty vessel coverage as a successful update,
|
||||||
clearing obsolete contacts while retaining stale or incomplete snapshots.
|
clearing obsolete contacts while retaining stale or incomplete snapshots.
|
||||||
|
|
||||||
|
|||||||
@@ -4066,7 +4066,8 @@ when its own signal aborts.
|
|||||||
Configured endpoints have per-instance cooldowns for 406/429, honoring
|
Configured endpoints have per-instance cooldowns for 406/429, honoring
|
||||||
Retry-After; absent delays back off from 30 seconds to five minutes. Empty
|
Retry-After; absent delays back off from 30 seconds to five minutes. Empty
|
||||||
`elements` is a valid answer. Existing byte, query, concurrency and cache caps
|
`elements` is a valid answer. Existing byte, query, concurrency and cache caps
|
||||||
remain. Consumer capability misses never schedule automatic retries.
|
remain. Malformed capability-probe responses remain unknown and are retried after the existing 30-second backoff; valid configured decisions remain memoized.
|
||||||
|
Consumer capability misses never schedule automatic retries.
|
||||||
|
|
||||||
Operator-configured Overpass area and focus-footprint queries use body-level
|
Operator-configured Overpass area and focus-footprint queries use body-level
|
||||||
`out geom` (with `center` for focus), preserving relation member outlines.
|
`out geom` (with `center` for focus), preserving relation member outlines.
|
||||||
|
|||||||
@@ -313,7 +313,9 @@ test('retry: a plain HTTP 500 still exhausts the existing 8s/25s transient ladde
|
|||||||
t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 100_000 });
|
t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 100_000 });
|
||||||
globalThis.window = { setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout };
|
globalThis.window = { setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout };
|
||||||
const requestTimes = [];
|
const requestTimes = [];
|
||||||
globalThis.fetch = async () => {
|
globalThis.fetch = async (url) => {
|
||||||
|
if (String(url).endsWith('/status'))
|
||||||
|
return Response.json({ configured: true });
|
||||||
requestTimes.push(Date.now());
|
requestTimes.push(Date.now());
|
||||||
return httpFailure(500);
|
return httpFailure(500);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { createApplicationRequestServices } from './requests.js';
|
||||||
|
test('unknown capability replies recover after backoff', async (t) => {
|
||||||
|
t.mock.timers.enable({ apis: ['Date'], now: 100_000 });
|
||||||
|
for (const first of [
|
||||||
|
() => Response.json({ wrong: true }),
|
||||||
|
() => new Response('bad', { status: 503 }),
|
||||||
|
]) {
|
||||||
|
let probes = 0;
|
||||||
|
let queries = 0;
|
||||||
|
const services = createApplicationRequestServices({
|
||||||
|
boundaryProbe: { timeoutMs: 1000, retryMs: 1000 },
|
||||||
|
fetchImpl: async (url) => {
|
||||||
|
if (url.endsWith('/status'))
|
||||||
|
return ++probes === 1
|
||||||
|
? first()
|
||||||
|
: Response.json({ configured: false });
|
||||||
|
queries++;
|
||||||
|
return Response.json({ elements: [] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.deepEqual(await services.boundaries.query('fixture'), []);
|
||||||
|
assert.deepEqual(await services.boundaries.query('fixture'), []);
|
||||||
|
assert.equal(probes, 1, 'unknown reply is not re-probed inside backoff');
|
||||||
|
t.mock.timers.tick(1000);
|
||||||
|
assert.equal(
|
||||||
|
(await services.boundaries.query('fixture')).code,
|
||||||
|
'OVERPASS_NOT_CONFIGURED',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
(await services.boundaries.query('fixture')).code,
|
||||||
|
'OVERPASS_NOT_CONFIGURED',
|
||||||
|
);
|
||||||
|
assert.equal(probes, 2);
|
||||||
|
assert.equal(queries, 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -66,9 +66,9 @@ export function createApplicationRequestServices({
|
|||||||
throw new Error(`${label} unavailable (${response.status})`);
|
throw new Error(`${label} unavailable (${response.status})`);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
// Learn once per page whether the server has an Overpass instance, so an
|
// Remember valid capability decisions for the page, so an
|
||||||
// unconfigured server is never asked (and never answers with an error).
|
// unconfigured server is never asked (and never answers with an error).
|
||||||
// Discovery is bounded; a failed or timed-out probe is forgotten and not
|
// Discovery is bounded; failed, malformed or timed-out probes are forgotten and not
|
||||||
// retried for a short backoff, during which queries go to the server as
|
// retried for a short backoff, during which queries go to the server as
|
||||||
// they would against an older server without the probe.
|
// they would against an older server without the probe.
|
||||||
let boundaryCapability = null;
|
let boundaryCapability = null;
|
||||||
@@ -79,10 +79,13 @@ export function createApplicationRequestServices({
|
|||||||
boundaryCapability = request(`${urls.boundaries}/status`, {
|
boundaryCapability = request(`${urls.boundaries}/status`, {
|
||||||
signal: AbortSignal.timeout(boundaryProbe.timeoutMs),
|
signal: AbortSignal.timeout(boundaryProbe.timeoutMs),
|
||||||
}).then(
|
}).then(
|
||||||
(response) =>
|
(response) => {
|
||||||
response.ok && typeof response.data?.configured === 'boolean'
|
if (response.ok && typeof response.data?.configured === 'boolean')
|
||||||
? response.data.configured
|
return response.data.configured;
|
||||||
: null, // an older server: fall back to asking per query
|
boundaryCapability = null;
|
||||||
|
boundaryProbeRetryAt = Date.now() + boundaryProbe.retryMs;
|
||||||
|
return null; // capability unknown: let this query ask the server
|
||||||
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
boundaryCapability = null;
|
boundaryCapability = null;
|
||||||
if (lifetime?.aborted) throw error;
|
if (lifetime?.aborted) throw error;
|
||||||
|
|||||||
Reference in new issue
Block a user