Skip to content

Commit 88a9af9

Browse files
committed
wip: jests for #4
removed, matrixai/id
1 parent 1893e1e commit 88a9af9

File tree

2 files changed

+204
-27
lines changed

2 files changed

+204
-27
lines changed

src/RPCServer.ts

-17
Original file line numberDiff line numberDiff line change
@@ -448,21 +448,6 @@ class RPCServer extends EventTarget {
448448
},
449449
});
450450

451-
// `RPCStream.cancel(reason)`.
452-
const handleAbort = () => {
453-
const timer = new Timer({
454-
delay: this.handlerTimeoutTime,
455-
handler: () => {
456-
rpcStream.cancel(abortController.signal.reason);
457-
},
458-
});
459-
void timer
460-
.catch(() => {}) // Ignore cancellation error
461-
.finally(() => {
462-
abortController.signal.removeEventListener('abort', handleAbort);
463-
});
464-
};
465-
abortController.signal.addEventListener('abort', handleAbort);
466451
const prom = (async () => {
467452
const id = await this.idGen();
468453
const headTransformStream = rpcUtilsMiddleware.binaryToJsonMessageStream(
@@ -606,7 +591,6 @@ class RPCServer extends EventTarget {
606591
await headerWriter.close();
607592
// Clean up and return
608593
timer.cancel(cleanupReason);
609-
abortController.signal.removeEventListener('abort', handleAbort);
610594
rpcStream.cancel(Error('TMP header message was an error'));
611595
return;
612596
}
@@ -629,7 +613,6 @@ class RPCServer extends EventTarget {
629613
this.logger.info(`Handled stream with method (${method})`);
630614
// Cleaning up abort and timer
631615
timer.cancel(cleanupReason);
632-
abortController.signal.removeEventListener('abort', handleAbort);
633616
abortController.abort(new rpcErrors.ErrorRPCStreamEnded());
634617
})();
635618
const handlerProm = PromiseCancellable.from(prom, abortController).finally(

tests/RPC.test.ts

+204-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import ServerCaller from '@/callers/ServerCaller';
1111
import ClientCaller from '@/callers/ClientCaller';
1212
import UnaryCaller from '@/callers/UnaryCaller';
1313
import * as rpcUtilsMiddleware from '@/utils/middleware';
14-
import { ErrorRPC, ErrorRPCRemote } from '@/errors';
14+
import {
15+
ErrorRPC,
16+
ErrorRPCHandlerFailed,
17+
ErrorRPCRemote,
18+
ErrorRPCTimedOut,
19+
} from '@/errors';
1520
import * as rpcErrors from '@/errors';
1621
import RPCClient from '@/RPCClient';
1722
import RPCServer from '@/RPCServer';
@@ -593,18 +598,207 @@ describe('RPC', () => {
593598
await expect(rpcServer.destroy(false)).toResolve();
594599
await rpcClient.destroy();
595600
});
601+
testProp(
602+
'RPC Client and server timeout concurrently',
603+
[fc.array(rpcTestUtils.safeJsonValueArb, { minLength: 1 })],
604+
async (values) => {
605+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
606+
Uint8Array,
607+
Uint8Array
608+
>();
609+
const timeout = 600;
610+
class TestMethod extends DuplexHandler {
611+
public handle = async function* (
612+
input: AsyncIterableIterator<JSONValue>,
613+
cancel: (reason?: any) => void,
614+
meta: Record<string, JSONValue> | undefined,
615+
ctx: ContextTimed,
616+
): AsyncIterableIterator<JSONValue> {
617+
// Check for abort event
618+
ctx.signal.throwIfAborted();
619+
const abortProm = utils.promise<never>();
620+
ctx.signal.addEventListener('abort', () => {
621+
abortProm.rejectP(ctx.signal.reason);
622+
});
623+
await abortProm.p;
624+
};
625+
}
626+
const testMethodInstance = new TestMethod({});
627+
// Set up a client and server with matching timeout settings
628+
const rpcServer = await RPCServer.createRPCServer({
629+
manifest: {
630+
testMethod: testMethodInstance,
631+
},
632+
logger,
633+
idGen,
634+
handlerTimeoutTime: timeout,
635+
});
636+
rpcServer.handleStream({
637+
...serverPair,
638+
cancel: () => {},
639+
});
640+
641+
const rpcClient = await RPCClient.createRPCClient({
642+
manifest: {
643+
testMethod: new DuplexCaller(),
644+
},
645+
streamFactory: async () => {
646+
return {
647+
...clientPair,
648+
cancel: () => {},
649+
};
650+
},
651+
logger,
652+
idGen,
653+
streamKeepAliveTimeoutTime: timeout,
654+
});
655+
const callerInterface = await rpcClient.methods.testMethod();
656+
const writer = callerInterface.writable.getWriter();
657+
const reader = callerInterface.readable.getReader();
658+
await expect(reader.read()).rejects.toThrow(
659+
'Timed out waiting for header',
660+
);
661+
await expect(writer.write()).rejects.toThrow(
662+
'Timed out waiting for header',
663+
);
664+
await rpcServer.destroy();
665+
await rpcClient.destroy();
666+
},
667+
{ numRuns: 1 },
668+
);
669+
testProp(
670+
'RPC server times out before client',
671+
[fc.array(rpcTestUtils.safeJsonValueArb, { minLength: 1 })],
672+
async (values) => {
673+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
674+
Uint8Array,
675+
Uint8Array
676+
>();
677+
class TestMethod extends DuplexHandler {
678+
public handle = async function* (
679+
input: AsyncIterableIterator<JSONValue>,
680+
cancel: (reason?: any) => void,
681+
meta: Record<string, JSONValue> | undefined,
682+
ctx: ContextTimed,
683+
): AsyncIterableIterator<JSONValue> {
684+
await utils.sleep(7); // Longer than server's timeout, shorter than client'
685+
yield* input;
686+
};
687+
}
688+
// Set up a client and server, server has a shorter timeout than client`
689+
const rpcServer = await RPCServer.createRPCServer({
690+
manifest: {
691+
testMethod: new TestMethod({}),
692+
},
693+
logger,
694+
idGen,
695+
handlerTimeoutTime: 5,
696+
});
697+
rpcServer.handleStream({
698+
...serverPair,
699+
cancel: () => {},
700+
});
596701

597-
/* test('RPC Client and server timeout concurrentrly'), async () => {};
598-
test('RPC client times out and attempts to reconnect'), async () => {};
702+
const rpcClient = await RPCClient.createRPCClient({
703+
manifest: {
704+
testMethod: new DuplexCaller(),
705+
},
706+
streamFactory: async () => {
707+
return {
708+
...clientPair,
709+
cancel: () => {},
710+
};
711+
},
712+
logger,
713+
idGen,
714+
streamKeepAliveTimeoutTime: 10,
715+
});
716+
const callerInterface = await rpcClient.methods.testMethod();
717+
const writer = callerInterface.writable.getWriter();
718+
const reader = callerInterface.readable.getReader();
719+
720+
// Expect the server to time out first
721+
await expect(writer.closed).rejects.toThrow(ErrorRPCHandlerFailed);
722+
await expect(reader.closed).rejects.toThrow(ErrorRPCHandlerFailed);
723+
724+
await rpcServer.destroy();
725+
await rpcClient.destroy();
726+
},
727+
);
728+
testProp(
729+
'Client times out before server',
730+
[fc.array(rpcTestUtils.safeJsonValueArb, { minLength: 1 })],
731+
async (values) => {
732+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
733+
Uint8Array,
734+
Uint8Array
735+
>();
736+
class TestMethod extends DuplexHandler {
737+
public handle = async function* (
738+
input: AsyncIterableIterator<JSONValue>,
739+
cancel: (reason?: any) => void,
740+
meta: Record<string, JSONValue> | undefined,
741+
ctx: ContextTimed,
742+
): AsyncIterableIterator<JSONValue> {
743+
await utils.sleep(7); // Longer than client's timeout, shorter than server's
744+
yield* input;
745+
};
746+
}
747+
// Set up a client and server with matching timeout settings
748+
const rpcServer = await RPCServer.createRPCServer({
749+
manifest: {
750+
testMethod: new TestMethod({}),
751+
},
752+
logger,
753+
idGen,
754+
handlerTimeoutTime: 10,
755+
});
756+
rpcServer.handleStream({
757+
...serverPair,
758+
cancel: () => {},
759+
});
760+
761+
const rpcClient = await RPCClient.createRPCClient({
762+
manifest: {
763+
testMethod: new DuplexCaller(),
764+
},
765+
streamFactory: async () => {
766+
return {
767+
...clientPair,
768+
cancel: () => {},
769+
};
770+
},
771+
logger,
772+
idGen,
773+
streamKeepAliveTimeoutTime: 5,
774+
});
775+
const callerInterface = await rpcClient.methods.testMethod();
776+
const writer = callerInterface.writable.getWriter();
777+
const reader = callerInterface.readable.getReader();
778+
779+
// Expect the client to time out first
780+
await expect(writer.closed).rejects.toThrow(ErrorRPCHandlerFailed);
781+
await expect(reader.closed).rejects.toThrow(ErrorRPCHandlerFailed);
599782

600-
test('RPC server times out before client'), async () => {};
601-
/!**
783+
await rpcServer.destroy();
784+
await rpcClient.destroy();
785+
},
786+
);
787+
/**
602788
* Hard timeout is absolute time limit, cannot be extended or bypassed. When reached,
603789
* operation is immediately cancelled.
604-
* Soft timeout has a threshold, but can be extended or bypassed. For e.g if server sends
605-
* partial response, client can extend the timeout to receive the rest of the response.
606-
*!/
607-
test('RPC server and client hard and soft timeout limits'), async () => {};
790+
* Soft timeout has a threshold, but can be extended or bypassed. For e.g if a server sends a
791+
* partial response, a client can extend the timeout to receive the rest of the response.
792+
*/
793+
test('RPC server and client hard and soft timeout limits', async () => {
794+
// Set up a client and server with both hard and soft timeout limits
795+
// Trigger various scenarios where one or both could hit soft or hard limits
796+
// Expect the system to behave according to the specified limits
797+
});
608798

609-
test('RPC client and server with infinite timeout'), async () => {};*/
799+
test('RPC client and server with infinite timeout', async () => {
800+
// Set up a client and server with infinite timeout settings
801+
// Trigger a call that will hang indefinitely or for a long time
802+
// Expect neither to time out and verify that they can still handle other operations
803+
});
610804
});

0 commit comments

Comments
 (0)