Skip to content

Commit 5684334

Browse files
authored
ensure cancel is idempotent (#354)
## Why we weren't sure calling ctx.cancel() twice was safe ## What changed add a test to assert and not regress on this behavior ## Versioning - [ ] Breaking protocol change - [ ] Breaking ts/js API change <!-- Kind reminder to add tests and updated documentation if needed -->
1 parent 1d02e1f commit 5684334

File tree

1 file changed

+75
-0
lines changed

1 file changed

+75
-0
lines changed

__tests__/cancellation.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,81 @@ describe.each(testMatrix())(
786786
server,
787787
});
788788
});
789+
790+
test('cancel is idempotent', async () => {
791+
const clientTransport = getClientTransport('client');
792+
const serverTransport = getServerTransport();
793+
794+
const clientProtocolError = vi.fn();
795+
const serverProtocolError = vi.fn();
796+
clientTransport.addEventListener('protocolError', clientProtocolError);
797+
serverTransport.addEventListener('protocolError', serverProtocolError);
798+
addPostTestCleanup(async () => {
799+
clientTransport.removeEventListener(
800+
'protocolError',
801+
clientProtocolError,
802+
);
803+
serverTransport.removeEventListener(
804+
'protocolError',
805+
serverProtocolError,
806+
);
807+
await cleanupTransports([clientTransport, serverTransport]);
808+
});
809+
810+
const handler = makeMockHandler('rpc');
811+
const services = {
812+
service: ServiceSchema.define({
813+
rpc: Procedure.rpc({
814+
requestInit: Type.Object({}),
815+
responseData: Type.Object({}),
816+
handler,
817+
}),
818+
}),
819+
};
820+
821+
const server = createServer(serverTransport, services);
822+
const client = createClient<typeof services>(
823+
clientTransport,
824+
serverTransport.clientId,
825+
);
826+
827+
const resP = client.service.rpc.rpc({});
828+
829+
await waitFor(() => {
830+
expect(handler).toHaveBeenCalledTimes(1);
831+
});
832+
833+
const [{ ctx }] = handler.mock.calls[0];
834+
const onRequestFinished = vi.fn();
835+
ctx.signal.addEventListener('abort', onRequestFinished);
836+
837+
ctx.cancel();
838+
839+
// calling cancel a second time should be a no-op
840+
ctx.cancel();
841+
842+
await waitFor(() => {
843+
expect(onRequestFinished).toHaveBeenCalled();
844+
});
845+
846+
// should only get one cancel error, not two
847+
await expect(resP).resolves.toEqual(
848+
Err({
849+
code: CANCEL_CODE,
850+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
851+
message: expect.any(String),
852+
}),
853+
);
854+
855+
expect(clientProtocolError).not.toHaveBeenCalled();
856+
expect(serverProtocolError).not.toHaveBeenCalled();
857+
858+
await testFinishesCleanly({
859+
clientTransports: [clientTransport],
860+
serverTransport,
861+
server,
862+
});
863+
});
789864
});
790865
},
791866
);

0 commit comments

Comments
 (0)