keep cancelled outline and search asks free during the Nominatim probe

Recheck the caller's signal right after the capability probe on every path, so an ask cleared while the probe is pending starts no outline or last-resort search request, whether the probe says configured, unknown or not configured.
This commit is contained in:
manjunath committed 2026-10-30 23:27:17 +05:30
1 parent f32ab5446b
commit 3824b4ebfc
4 files changed
+68 -6

No files matched your search

+6 -5
View File
@@ -80,11 +80,12 @@ export function createDefaultPlaceSearch({
createGoogleGeocoder({ createGoogleGeocoder({
async request(query, { bias, signal }) { async request(query, { bias, signal }) {
// The app's own route says up front when Nominatim is off. // The app's own route says up front when Nominatim is off.
if ( const capability = nominatimConfigured
nominatimConfigured && ? await nominatimConfigured()
(await nominatimConfigured()) === false : null;
) // A search cancelled during the probe starts no request.
return null; signal?.throwIfAborted();
if (capability === false) return null;
const params = new URLSearchParams({ q: query }); const params = new URLSearchParams({ q: query });
if (bias) params.set('bounds', bias); if (bias) params.set('bounds', bias);
return fetchImpl( return fetchImpl(
+28
View File
@@ -53,3 +53,31 @@ test('the last-resort route is skipped when the server reports Nominatim unconfi
const on = await run(true); const on = await run(true);
assert.ok(on.includes('/api/geocode'), on.join(',')); assert.ok(on.includes('/api/geocode'), on.join(','));
}); });
test('a search cancelled while the Nominatim probe is pending starts no last-resort request', async () => {
let release;
const held = new Promise((resolve) => {
release = resolve;
});
const asked = [];
const controller = new AbortController();
const search = createDefaultPlaceSearch({
endpoints: { photon: null },
fetchImpl: async (url) => {
const path = new URL(String(url), 'http://local.test').pathname;
asked.push(path);
if (path === '/api/geocode/status') {
controller.abort();
await held;
return Response.json({ configured: true });
}
return Response.json({ status: 'ZERO_RESULTS', results: [] });
},
});
const pending = search.geocode('nowhere-cancelled', {
signal: controller.signal,
});
release();
await pending.catch(() => null);
assert.ok(!asked.includes('/api/geocode'), asked.join(','));
});
+3 -1
View File
@@ -96,8 +96,10 @@ export function createNominatimOutlineClient({
if (remembered.has(key)) return remembered.get(key); if (remembered.has(key)) return remembered.get(key);
// A cancelled ask spends nothing. // A cancelled ask spends nothing.
signal?.throwIfAborted(); signal?.throwIfAborted();
if (configured && (await configured()) === false) { const capability = configured ? await configured() : null;
// The probe may have waited: an ask cancelled meanwhile still spends nothing.
signal?.throwIfAborted(); signal?.throwIfAborted();
if (capability === false) {
unavailable = { unavailable = {
unavailable: true, unavailable: true,
retryable: false, retryable: false,
+31
View File
@@ -179,3 +179,34 @@ test('a configured server is asked as usual after one probe', async () => {
assert.equal(calls.length, 2); assert.equal(calls.length, 2);
assert.equal(probes.length, 1); assert.equal(probes.length, 1);
}); });
test('an ask cancelled while the capability probe is pending spends nothing', async () => {
for (const [label, answer] of [
['configured', () => reply(200, { configured: true })],
['unknown', () => reply(404, { error: 'Not Found' })],
['unconfigured', () => reply(200, { configured: false })],
]) {
let release;
const held = new Promise((resolve) => {
release = resolve;
});
const { calls, lookup } = client(
() => reply(200, { status: 'ZERO_RESULTS', outline: null }),
{
status: async () => {
await held;
return answer();
},
},
);
const controller = new AbortController();
const pending = lookup(
{ query: 'Paris', kind: 'city' },
{ signal: controller.signal },
);
controller.abort();
release();
await assert.rejects(pending, { name: 'AbortError' }, label);
assert.equal(calls.length, 0, `${label}: no outline request`);
}
});