Spaces:
Runtime error
Runtime error
File size: 1,630 Bytes
46252cd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import { IngressJobData } from '../queue/processors/ingress.processor';
// Single-node, in-process keyed lock: a chain of promises per key. The ordering key is a plain
// string (no closures) so a distributed lock (e.g. Redis redlock / SET NX PX) can replace this
// verbatim once ingress runs on more than one node.
export class KeyedAsyncLock {
private readonly tails = new Map<string, Promise<unknown>>();
run<T>(key: string, fn: () => Promise<T>): Promise<T> {
const prev = this.tails.get(key) ?? Promise.resolve();
// Chain fn after prev; swallow prev's rejection so one failure doesn't poison the chain.
const next = prev.catch(() => undefined).then(fn);
this.tails.set(key, next);
// Clean up the map entry once this is the tail and it settles, to avoid unbounded growth.
void next
.catch(() => undefined)
.finally(() => {
if (this.tails.get(key) === next) this.tails.delete(key);
});
return next;
}
}
// Key on the CONVERSATION, never the deliveryId (a delivery-unique key would serialize nothing).
// providerConversationId is populated host-side when the plugin manifest declares a conversationId
// pointer; absent it, we serialize per instance — correct (never reorders within a conversation) but
// coarse. Per-instance fallback is the lazy-correct default; the conversation key unlocks
// intra-instance parallelism once the manifest declares the pointer.
export function orderingKeyFor(job: IngressJobData): string {
if (job.providerConversationId) return `${job.instanceId}:${job.providerConversationId}`;
return `instance:${job.instanceId}`;
}
|