Skip to content

Commit

Permalink
The coalescing transport no longer double-throws AbortError when th…
Browse files Browse the repository at this point in the history
…e last subscriber aborts (#2029)
  • Loading branch information
steveluscher authored Jan 12, 2024
1 parent 62b6bd6 commit dbe6a73
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 14 deletions.
23 changes: 13 additions & 10 deletions packages/library/src/__tests__/rpc-request-coalescer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('RPC request coalescer', () => {
beforeEach(() => {
jest.useFakeTimers();
hashFn = jest.fn();
mockTransport = jest.fn();
mockTransport = jest.fn().mockResolvedValue(null);
coalescedTransport = getRpcTransportWithRequestCoalescing(mockTransport, hashFn);
});
describe('when requests produce the same hash', () => {
Expand Down Expand Up @@ -88,12 +88,16 @@ describe('RPC request coalescer', () => {
beforeEach(() => {
abortControllerA = new AbortController();
abortControllerB = new AbortController();
mockTransport.mockImplementation(
() =>
new Promise(resolve => {
transportResponsePromise = resolve;
}),
);
mockTransport.mockImplementation(async ({ signal }) => {
signal?.throwIfAborted();
return await new Promise((resolve, reject) => {
transportResponsePromise = resolve;
signal?.addEventListener('abort', (e: AbortSignalEventMap['abort']) => {
const abortError = new DOMException((e.target as AbortSignal).reason, 'AbortError');
reject(abortError);
});
});
});
responsePromiseA = coalescedTransport({ payload: null, signal: abortControllerA.signal });
responsePromiseB = coalescedTransport({ payload: null, signal: abortControllerB.signal });
});
Expand All @@ -110,13 +114,12 @@ describe('RPC request coalescer', () => {
abortControllerA.abort('o no');
await expect(responsePromiseA).rejects.toThrow(/o no/);
});
it('aborts the transport when all of the requests abort', async () => {
expect.assertions(1);
it('aborts the transport when all of the requests abort', () => {
abortControllerA.abort('o no A');
abortControllerB.abort('o no B');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const transportAbortSignal = mockTransport.mock.lastCall![0].signal!;
await expect(transportAbortSignal.aborted).toBe(true);
expect(transportAbortSignal.aborted).toBe(true);
});
it('does not abort the transport if fewer than every request aborts', async () => {
expect.assertions(1);
Expand Down
23 changes: 19 additions & 4 deletions packages/library/src/rpc-request-coalescer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,28 @@ export function getRpcTransportWithRequestCoalescing(
}
if (coalescedRequestsByDeduplicationKey[deduplicationKey] == null) {
const abortController = new AbortController();
const responsePromise = (async () => {
try {
return await transport<TResponse>({
...config,
signal: abortController.signal,
});
} catch (e) {
if (e && typeof e === 'object' && 'name' in e && e.name === 'AbortError') {
// Ignore `AbortError` thrown from the underlying transport behind which all
// requests are coalesced. If it experiences an `AbortError` it is because
// we triggered one when the last subscriber aborted. Letting the underlying
// transport's `AbortError` bubble up from here would cause runtime fatals
// where there should be none.
return;
}
throw e;
}
})();
coalescedRequestsByDeduplicationKey[deduplicationKey] = {
abortController,
numConsumers: 0,
responsePromise: transport<TResponse>({
...config,
signal: abortController.signal,
}),
responsePromise,
};
}
const coalescedRequest = coalescedRequestsByDeduplicationKey[deduplicationKey];
Expand Down

0 comments on commit dbe6a73

Please sign in to comment.