mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix: clear GhostMaps outage state on unchanged-head recovery
This commit is contained in:
1 parent
d5c49d35e8
commit
cb11e7dac8
4 files changed
+62
-1
No files matched your search
@@ -5,3 +5,5 @@
|
|||||||
cards on click. `/api/ghostmaps` watches the repository's branch head every
|
cards on click. `/api/ghostmaps` watches the repository's branch head every
|
||||||
15 s and pushes each new commit to open pages, which reload only the
|
15 s and pushes each new commit to open pages, which reload only the
|
||||||
products that changed (Black Pine Defense).
|
products that changed (Black Pine Defense).
|
||||||
|
- Clear a temporary GhostMaps upstream error and notify open pages when the
|
||||||
|
repository becomes reachable again, including when no new edition was published.
|
||||||
@@ -5128,6 +5128,7 @@ are omitted rather than framing the wrong part of the globe.
|
|||||||
- **Updates.** The repository is not ours, so there is no webhook. `server/providers/ghostMaps.js` starts with the first `/api/ghostmaps` request, so an install that never enables the layer never contacts GitHub.
|
- **Updates.** The repository is not ours, so there is no webhook. `server/providers/ghostMaps.js` starts with the first `/api/ghostmaps` request, so an install that never enables the layer never contacts GitHub.
|
||||||
- It reads the branch head from git's smart-HTTP ref advertisement (`/info/refs?service=git-upload-pack`, about 0.5 KB, outside the REST rate limit) every `GHOSTMAPS_POLL_SECONDS` (15 s by default, 5 s minimum). Failures back off from 15 s to 5 min.
|
- It reads the branch head from git's smart-HTTP ref advertisement (`/info/refs?service=git-upload-pack`, about 0.5 KB, outside the REST rate limit) every `GHOSTMAPS_POLL_SECONDS` (15 s by default, 5 s minimum). Failures back off from 15 s to 5 min.
|
||||||
- Only a new head costs REST calls: one recursive tree and one commit. An optional `GITHUB_TOKEN` raises the REST limit.
|
- Only a new head costs REST calls: one recursive tree and one commit. An optional `GITHUB_TOKEN` raises the REST limit.
|
||||||
|
- After an upstream outage, a successful branch-head check clears the error and pushes a recovered catalog even when the commit is unchanged. Cached products stay in place; subsequent healthy unchanged checks stay silent.
|
||||||
- The new catalog goes to `.gev-cache/ghostmaps/catalog.json` and to every open page over server-sent events (`/api/ghostmaps/events`, 25 s heartbeat, at most 64 subscribers). Default products are downloaded at once.
|
- The new catalog goes to `.gev-cache/ghostmaps/catalog.json` and to every open page over server-sent events (`/api/ghostmaps/events`, 25 s heartbeat, at most 64 subscribers). Default products are downloaded at once.
|
||||||
- The layer reloads only products whose blob changed. The new edition's data source is added before the old one is removed. A product that leaves the catalog leaves the globe.
|
- The layer reloads only products whose blob changed. The new edition's data source is added before the old one is removed. A product that leaves the catalog leaves the globe.
|
||||||
- The row reads `LIVE` while the stream is open. A five-minute catalog read is the backstop.
|
- The row reads `LIVE` while the stream is open. A five-minute catalog read is the backstop.
|
||||||
|
|||||||
@@ -172,7 +172,13 @@ export function createGhostMapsWatcher({
|
|||||||
await loadDisk();
|
await loadDisk();
|
||||||
const head = await readHead();
|
const head = await readHead();
|
||||||
state.checkedAt = now();
|
state.checkedAt = now();
|
||||||
if (head === state.commit) return false;
|
if (head === state.commit) {
|
||||||
|
if (state.error !== null) {
|
||||||
|
state.error = null;
|
||||||
|
emit();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const next = await readCommit(head);
|
const next = await readCommit(head);
|
||||||
const previous = new Map(state.products.map((p) => [p.id, p.version]));
|
const previous = new Map(state.products.map((p) => [p.id, p.version]));
|
||||||
const changed = next.products
|
const changed = next.products
|
||||||
|
|||||||
@@ -178,6 +178,58 @@ test('the catalog survives a restart from disk, and upstream failures are report
|
|||||||
assert.match(catalog.error, /HTTP 503/);
|
assert.match(catalog.error, /HTTP 503/);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
test('an unchanged head clears an upstream outage and announces recovery once', () =>
|
||||||
|
withCache(async (cacheDir) => {
|
||||||
|
const { fetchImpl } = fakeGitHub();
|
||||||
|
let unavailable = false;
|
||||||
|
let checkedAt = 1000;
|
||||||
|
const requests = [];
|
||||||
|
const watcher = createGhostMapsWatcher({
|
||||||
|
cacheDir,
|
||||||
|
now: () => checkedAt,
|
||||||
|
fetchImpl: async (url, options) => {
|
||||||
|
requests.push(url);
|
||||||
|
if (unavailable) return new Response('down', { status: 503 });
|
||||||
|
return fetchImpl(url, options);
|
||||||
|
},
|
||||||
|
log: () => {},
|
||||||
|
warn: () => {},
|
||||||
|
});
|
||||||
|
const events = [];
|
||||||
|
watcher.subscribe((catalog) => events.push(catalog));
|
||||||
|
await watcher.checkNow();
|
||||||
|
await Promise.all([watcher.file('cip'), watcher.file('kinetic-30d')]);
|
||||||
|
const healthy = watcher.catalog();
|
||||||
|
assert.equal(events.length, 1);
|
||||||
|
|
||||||
|
unavailable = true;
|
||||||
|
checkedAt = 2000;
|
||||||
|
assert.equal(await watcher.checkNow(), false);
|
||||||
|
assert.match(watcher.catalog().error, /HTTP 503/);
|
||||||
|
assert.equal(events.length, 2);
|
||||||
|
|
||||||
|
unavailable = false;
|
||||||
|
checkedAt = 3000;
|
||||||
|
requests.length = 0;
|
||||||
|
assert.equal(await watcher.checkNow(), false, 'no new edition arrived');
|
||||||
|
const recovered = watcher.catalog();
|
||||||
|
assert.equal(recovered.error, null);
|
||||||
|
assert.equal(recovered.checkedAt, checkedAt);
|
||||||
|
assert.equal(recovered.commit, healthy.commit);
|
||||||
|
assert.equal(recovered.changedAt, healthy.changedAt);
|
||||||
|
assert.deepEqual(recovered.products, healthy.products);
|
||||||
|
assert.equal(requests.length, 1, 'recovery needs only the ref read');
|
||||||
|
assert.match(requests[0], /\/info\/refs\?service=git-upload-pack$/);
|
||||||
|
assert.equal(events.length, 3, 'subscribers see the recovered catalog');
|
||||||
|
assert.equal(events[2].error, null);
|
||||||
|
assert.equal(events[2].checkedAt, checkedAt);
|
||||||
|
|
||||||
|
checkedAt = 4000;
|
||||||
|
assert.equal(await watcher.checkNow(), false);
|
||||||
|
assert.equal(events.length, 3, 'healthy unchanged polls stay silent');
|
||||||
|
watcher.stop();
|
||||||
|
}));
|
||||||
|
|
||||||
test('poll interval is configurable with a 5 s floor', () => {
|
test('poll interval is configurable with a 5 s floor', () => {
|
||||||
assert.equal(ghostMapsPollMs(undefined), 15000);
|
assert.equal(ghostMapsPollMs(undefined), 15000);
|
||||||
assert.equal(ghostMapsPollMs('30'), 30000);
|
assert.equal(ghostMapsPollMs('30'), 30000);
|
||||||
|
|||||||
Reference in new issue
Block a user