From 89af83c660e21e65b4f0b5922b5515c774f99d11 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 4 Jan 2024 02:09:23 -0600 Subject: [PATCH] search: fix abort signals --- src/controllers/api/search.ts | 12 +++++++----- src/storages/hydrate.ts | 5 +++-- src/storages/search-store.ts | 13 +++++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index 2b640d5..d18694c 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -30,9 +30,11 @@ const searchController: AppController = async (c) => { return c.json({ error: 'Bad request', schema: result.error }, 422); } + const signal = AbortSignal.timeout(1000); + const [event, events] = await Promise.all([ - lookupEvent(result.data), - searchEvents(result.data), + lookupEvent(result.data, signal), + searchEvents(result.data, signal), ]); if (event) { @@ -62,7 +64,7 @@ const searchController: AppController = async (c) => { }; /** Get events for the search params. */ -function searchEvents({ q, type, limit, account_id }: SearchQuery): Promise { +function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: AbortSignal): Promise { if (type === 'hashtags') return Promise.resolve([]); const filter: DittoFilter = { @@ -76,7 +78,7 @@ function searchEvents({ q, type, limit, account_id }: SearchQuery): Promise { +async function lookupEvent(query: SearchQuery, signal: AbortSignal): Promise { const filters = await getLookupFilters(query); const [event] = await searchStore.getEvents(filters, { limit: 1, signal }); return event; diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index 3edca14..4d8edcb 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -5,15 +5,16 @@ interface HydrateEventOpts { events: DittoEvent[]; filters: DittoFilter[]; storage: EventStore; + signal?: AbortSignal; } /** Hydrate event relationships using the provided storage. */ async function hydrateEvents(opts: HydrateEventOpts): Promise[]> { - const { events, filters, storage } = opts; + const { events, filters, storage, signal } = opts; if (filters.some((filter) => filter.relations?.includes('author'))) { const pubkeys = new Set([...events].map((event) => event.pubkey)); - const authors = await storage.getEvents([{ kinds: [0], authors: [...pubkeys] }]); + const authors = await storage.getEvents([{ kinds: [0], authors: [...pubkeys] }], { signal }); for (const event of events) { event.author = authors.find((author) => author.pubkey === event.pubkey); diff --git a/src/storages/search-store.ts b/src/storages/search-store.ts index a289734..30e6ba1 100644 --- a/src/storages/search-store.ts +++ b/src/storages/search-store.ts @@ -50,14 +50,23 @@ class SearchStore implements EventStore { this.#debug(`Searching for "${query}" at ${this.#relay.socket.url}...`); const sub = this.#relay.req(filters, opts); - sub.eoseSignal.onabort = () => sub.close(); + + const close = () => { + sub.close(); + opts?.signal?.removeEventListener('abort', close); + sub.eoseSignal.removeEventListener('abort', close); + }; + + opts?.signal?.addEventListener('abort', close, { once: true }); + sub.eoseSignal.addEventListener('abort', close, { once: true }); + const events = new EventSet>(); for await (const event of sub) { events.add(event); } - return hydrateEvents({ events: [...events], filters, storage: this.#hydrator }); + return hydrateEvents({ events: [...events], filters, storage: this.#hydrator, signal: opts?.signal }); } else { this.#debug(`Searching for "${query}" locally...`); return this.#fallback.getEvents(filters, opts);