mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 14:22:21 +00:00
docs(routes): tighten the outage comments and notes
Shorter comments, changelog and CURRENT-STATE text for the 502 outage path; the CURRENT-STATE paragraph now also names the 429 exception and which budget expiry answers busy and which answers 502. Text only.
This commit is contained in:
1 parent
a29611ca17
commit
82e2b716bc
7 files changed
+20
-36
No files matched your search
@@ -1,4 +1,3 @@
|
|||||||
- When the routing service is down (an error status, no answer or a non-JSON
|
- Routing outages report "Routing service unavailable" in voice route planning
|
||||||
body), voice route planning hears "Routing service unavailable" and the
|
and "Routing unavailable" in Directions instead of "no route found"
|
||||||
Directions layer shows "Routing unavailable" instead of "no route found",
|
(quanticsoul4772, #1509).
|
||||||
so neither blames the request (quanticsoul4772, #1509).
|
|
||||||
+8
-11
@@ -5447,16 +5447,13 @@ service's usage policy states; past a bounded queue the answer is a 429 with a
|
|||||||
`Retry-After` rather than a queue that grows until everything times out. The
|
`Retry-After` rather than a queue that grows until everything times out. The
|
||||||
upstream host is pinned and a redirect is refused rather than followed, and a
|
upstream host is pinned and a redirect is refused rather than followed, and a
|
||||||
rate limit from the routing service is reported as one instead of as "no route
|
rate limit from the routing service is reported as one instead of as "no route
|
||||||
found". So is an outage: any failure status but 400 (a 5xx, a 403 block, a
|
found". Upstream failure statuses except 400 and 429, network failures, and
|
||||||
404), no answer (timeout, network error) or a body that is not JSON answers
|
unreadable, oversized or non-JSON bodies return `502 routing service unavailable`.
|
||||||
`502 routing service unavailable`. `plan_route` throws `Routing service
|
Voice hears `Routing service unavailable (HTTP 502)`; Directions shows
|
||||||
unavailable (HTTP 502)`, which is what voice hears, and the layer shows
|
`Routing unavailable (HTTP 502)`. OSRM 400 responses or successful responses
|
||||||
`Routing unavailable (HTTP 502)`; only OSRM's own refusal (a 400, or a 200
|
without a usable route return "no route found". The 12 s upstream budget includes
|
||||||
without a route) reads "no route found". The 12 s timeout runs from the
|
queue time: expiry before dispatch returns 429 "routing busy" when the queue slot
|
||||||
request's arrival, queue wait included; a budget spent before the request
|
opens; expiry during the upstream request returns 502.
|
||||||
leaves the queue answers 429 "routing busy". A request that waited in the
|
|
||||||
queue gives OSRM the rest of the 12 s, so a slow answer then still reads as
|
|
||||||
unavailable.
|
|
||||||
`src/data/routeSteps.js` turns OSRM maneuver type / modifier / exit /
|
`src/data/routeSteps.js` turns OSRM maneuver type / modifier / exit /
|
||||||
road name into one plain-English sentence per decision, folding
|
road name into one plain-English sentence per decision, folding
|
||||||
`exit roundabout` steps into the roundabout they leave, and caps one route at
|
`exit roundabout` steps into the roundabout they leave, and caps one route at
|
||||||
@@ -6867,7 +6864,7 @@ apply to `public/` assets or built-preview serving.
|
|||||||
- `/api/google/text-search` keeps the Google key server-side for view-biased Places recovery used by annotation resolution.
|
- `/api/google/text-search` keeps the Google key server-side for view-biased Places recovery used by annotation resolution.
|
||||||
- `/api/overpass` is bounded by body/response caps, per-client/global rate limits, concurrency limits, operator-configured upstreams, in-flight dedupe, cache bounds, and static validation that every selector is spatially bounded.
|
- `/api/overpass` is bounded by body/response caps, per-client/global rate limits, concurrency limits, operator-configured upstreams, in-flight dedupe, cache bounds, and static validation that every selector is spatially bounded.
|
||||||
- `/api/military-installations` uses an independent limiter with the same 90-per-client/300-global one-minute bounds, so viewport installation refreshes never consume `/api/overpass` annotation capacity.
|
- `/api/military-installations` uses an independent limiter with the same 90-per-client/300-global one-minute bounds, so viewport installation refreshes never consume `/api/overpass` annotation capacity.
|
||||||
- `/api/route` proxies bounded OSRM route requests for annotation routes, with profile allowlisting, distance caps, response caps, caching, and sanitized "no route found" errors; a routing-service outage answers 502 instead.
|
- `/api/route` proxies bounded OSRM route requests for annotation routes, with profile allowlisting, distance caps, response caps, caching, and sanitized "no route found" errors; routing outages return 502.
|
||||||
- `/api/flights`, `/api/military`, and `/api/route` answer `405 Method Not Allowed` with `Allow: GET` to anything but `GET`, before rate limiting and any upstream call.
|
- `/api/flights`, `/api/military`, and `/api/route` answer `405 Method Not Allowed` with `Allow: GET` to anything but `GET`, before rate limiting and any upstream call.
|
||||||
- `/api/flights` negotiates gzip. The cached global snapshot and the regional adsb.lol fallback are each gzipped once per refresh (about 860 KB becomes about 250 KB). Clients that offer gzip get that frame; other clients, and bodies under 1 KiB or that gzip cannot shrink, get identity bytes. Both answers carry `Vary: Accept-Encoding`. Uncached bodies, such as auth failures, are never sent under a cached frame's encoding.
|
- `/api/flights` negotiates gzip. The cached global snapshot and the regional adsb.lol fallback are each gzipped once per refresh (about 860 KB becomes about 250 KB). Clients that offer gzip get that frame; other clients, and bodies under 1 KiB or that gzip cannot shrink, get identity bytes. Both answers carry `Vary: Accept-Encoding`. Uncached bodies, such as auth failures, are never sent under a cached frame's encoding.
|
||||||
- Track endpoints: `/api/vessels/track?mmsi=` (server-accumulated ring buffers; sub-route handled before the rows snapshot), `/api/flights/track?icao24=` (OAuth, 60s cache, sanitized errors, independent OpenSky credit bucket), `/api/military/track?hex=` (400 for an invalid hex, otherwise `503 history_unavailable` with `Retry-After: 1800` and `Cache-Control: no-store`, no upstream request; military tracking uses its locally accumulated trail).
|
- Track endpoints: `/api/vessels/track?mmsi=` (server-accumulated ring buffers; sub-route handled before the rows snapshot), `/api/flights/track?icao24=` (OAuth, 60s cache, sanitized errors, independent OpenSky credit bucket), `/api/military/track?hex=` (400 for an invalid hex, otherwise `503 history_unavailable` with `Retry-After: 1800` and `Cache-Control: no-store`, no upstream request; military tracking uses its locally accumulated trail).
|
||||||
|
|||||||
@@ -72,10 +72,7 @@ class UpstreamRateLimitError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Thrown when the routing service failed or sent no JSON; answered as 502. */
|
||||||
* Thrown when the routing service failed, did not answer (timeout, network
|
|
||||||
* error, refused redirect) or did not answer with JSON.
|
|
||||||
*/
|
|
||||||
class UpstreamUnavailableError extends Error {
|
class UpstreamUnavailableError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('routing service unavailable');
|
super('routing service unavailable');
|
||||||
@@ -178,14 +175,12 @@ async function fetchRoute({
|
|||||||
`${base.replace(/\/$/, '')}/route/v1/${osrmProfile}/${coords}` +
|
`${base.replace(/\/$/, '')}/route/v1/${osrmProfile}/${coords}` +
|
||||||
`?overview=full&geometries=geojson&alternatives=false&steps=${withSteps ? 'true' : 'false'}`;
|
`?overview=full&geometries=geojson&alternatives=false&steps=${withSteps ? 'true' : 'false'}`;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
// One 12 s budget from arrival, queue wait included, so callers sized to it
|
// The 12 s budget includes queue wait, so Directions (15 s) hears back.
|
||||||
// (the Directions layer gives up at 15 s) hear back in time.
|
|
||||||
const timer = setTimeout(() => controller.abort(), 12000);
|
const timer = setTimeout(() => controller.abort(), 12000);
|
||||||
let osrm;
|
let osrm;
|
||||||
try {
|
try {
|
||||||
const upstreamRes = await throughUpstreamGate(async () => {
|
const upstreamRes = await throughUpstreamGate(async () => {
|
||||||
// A budget spent waiting in our own queue is our backlog, not the
|
// Expiry before dispatch reflects our backlog, not an upstream outage.
|
||||||
// routing service failing.
|
|
||||||
if (controller.signal.aborted)
|
if (controller.signal.aborted)
|
||||||
throw new RouteBusyError(_upstreamIntervalMs);
|
throw new RouteBusyError(_upstreamIntervalMs);
|
||||||
try {
|
try {
|
||||||
@@ -207,8 +202,7 @@ async function fetchRoute({
|
|||||||
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : 5,
|
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : 5,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// OSRM answers 400 for a request it cannot route (NoRoute, NoSegment);
|
// OSRM answers 400 for a route it cannot find; other failures are outages.
|
||||||
// any other failure status (a 5xx, a 403 block, a 404) is the service's.
|
|
||||||
if (upstreamRes.status === 400) {
|
if (upstreamRes.status === 400) {
|
||||||
await cancelBody(upstreamRes);
|
await cancelBody(upstreamRes);
|
||||||
return { payload: null, error: 'no route found' };
|
return { payload: null, error: 'no route found' };
|
||||||
@@ -222,9 +216,7 @@ async function fetchRoute({
|
|||||||
await cancelBody(upstreamRes);
|
await cancelBody(upstreamRes);
|
||||||
throw new UpstreamUnavailableError();
|
throw new UpstreamUnavailableError();
|
||||||
}
|
}
|
||||||
// A body cut off by the timeout or the network, an oversized one and one
|
// Read and parse failures are outages; later ones stay "route proxy error".
|
||||||
// that is not JSON are all the service's failure; an exception after this
|
|
||||||
// point is ours and stays a "route proxy error".
|
|
||||||
try {
|
try {
|
||||||
osrm = JSON.parse(
|
osrm = JSON.parse(
|
||||||
await readResponseTextCapped(upstreamRes, ROUTE_MAX_RESPONSE_BYTES),
|
await readResponseTextCapped(upstreamRes, ROUTE_MAX_RESPONSE_BYTES),
|
||||||
@@ -392,7 +384,6 @@ export function installRouteMiddleware(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (upstreamError instanceof UpstreamUnavailableError) {
|
if (upstreamError instanceof UpstreamUnavailableError) {
|
||||||
// Same reason: an outage says nothing about whether a route exists.
|
|
||||||
return unavailable(upstreamError.message);
|
return unavailable(upstreamError.message);
|
||||||
}
|
}
|
||||||
throw upstreamError;
|
throw upstreamError;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function createDirectionsSource({
|
|||||||
signal,
|
signal,
|
||||||
headers: { Accept: 'application/json' },
|
headers: { Accept: 'application/json' },
|
||||||
});
|
});
|
||||||
// A failure page in front of the proxy need not be JSON.
|
// Proxy error pages may not be JSON.
|
||||||
let payload = null;
|
let payload = null;
|
||||||
try {
|
try {
|
||||||
payload = await response.json();
|
payload = await response.json();
|
||||||
@@ -45,8 +45,7 @@ export function createDirectionsSource({
|
|||||||
{ status: 429, refusal },
|
{ status: 429, refusal },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// A 5xx is a routing outage whatever its body says; only the proxy's
|
// A 5xx body may mislabel an outage as a route refusal.
|
||||||
// refusals carry an error text worth showing.
|
|
||||||
if (response.status >= 500 || (!response.ok && !payload?.error))
|
if (response.status >= 500 || (!response.ok && !payload?.error))
|
||||||
throw Object.assign(
|
throw Object.assign(
|
||||||
new Error(`Routing unavailable (HTTP ${response.status})`),
|
new Error(`Routing unavailable (HTTP ${response.status})`),
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ test('OSRM routing preserves aliases, cache, span guards and upstream failure be
|
|||||||
assert.equal((await request(route, url)).body.error, error);
|
assert.equal((await request(route, url)).body.error, error);
|
||||||
assert.equal(calls, 1);
|
assert.equal(calls, 1);
|
||||||
now += 600001;
|
now += 600001;
|
||||||
// OSRM's own "no route" is a 400 with a JSON code, and stays one.
|
|
||||||
t.mock.method(globalThis, 'fetch', async () =>
|
t.mock.method(globalThis, 'fetch', async () =>
|
||||||
Response.json(
|
Response.json(
|
||||||
{ code: 'NoRoute', message: 'Impossible route between points' },
|
{ code: 'NoRoute', message: 'Impossible route between points' },
|
||||||
|
|||||||
+1
-2
@@ -189,8 +189,7 @@ export function createRouteService({
|
|||||||
} catch {
|
} catch {
|
||||||
/* the status below is authoritative */
|
/* the status below is authoritative */
|
||||||
}
|
}
|
||||||
// A rate limit or an outage (5xx) is the routing service's failure, not
|
// Handle 429 and 5xx before route refusals, regardless of the body.
|
||||||
// a missing route, whatever the body says. Voice hears this message.
|
|
||||||
if (response.status === 429) throw httpFailure(response, 'Routing');
|
if (response.status === 429) throw httpFailure(response, 'Routing');
|
||||||
if (response.status >= 500)
|
if (response.status >= 500)
|
||||||
throw new ToolError(
|
throw new ToolError(
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ test('a routing outage is reported as unavailable, not as invalid arguments', as
|
|||||||
assert.equal(result.structuredContent.error, 'unavailable');
|
assert.equal(result.structuredContent.error, 'unavailable');
|
||||||
assert.doesNotMatch(result.content[0].text, /No walk route/);
|
assert.doesNotMatch(result.content[0].text, /No walk route/);
|
||||||
}
|
}
|
||||||
// Voice passes only the message on, so it names the outage itself.
|
// Voice receives only the message, so it must name the outage.
|
||||||
const outage = createRouteService({
|
const outage = createRouteService({
|
||||||
fetchImpl: async () =>
|
fetchImpl: async () =>
|
||||||
Response.json(
|
Response.json(
|
||||||
|
|||||||
Reference in new issue
Block a user