Skip to content

Commit caa9bda

Browse files
committed
fix: server handlers are now able to persist operations and ignore timeouts
fix: changes to `ObjectEmpty` type to be in line with polykey [ci-skip]
1 parent 228f824 commit caa9bda

File tree

6 files changed

+96
-11
lines changed

6 files changed

+96
-11
lines changed

src/RPCServer.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ class RPCServer {
287287
// Input generator derived from the forward stream
288288
const inputGen = async function* (): AsyncIterable<I> {
289289
for await (const data of forwardStream) {
290-
ctx.timer.refresh();
290+
if (ctx.timer.status !== 'settled') {
291+
ctx.timer.refresh();
292+
}
291293
yield data.params as I;
292294
}
293295
};
@@ -296,7 +298,9 @@ class RPCServer {
296298
timer: ctx.timer,
297299
});
298300
for await (const response of handlerG) {
299-
ctx.timer.refresh();
301+
if (ctx.timer.status !== 'settled') {
302+
ctx.timer.refresh();
303+
}
300304
const responseMessage: JSONRPCResponseResult = {
301305
jsonrpc: '2.0',
302306
result: response,
@@ -570,13 +574,16 @@ class RPCServer {
570574
}
571575
// Setting up Timeout logic
572576
const timeout = this.defaultTimeoutMap.get(method);
573-
if (timeout != null && timeout < this.handlerTimeoutTime) {
574-
// Reset timeout with new delay if it is less than the default
575-
timer.reset(timeout);
576-
} else {
577-
// Otherwise refresh
578-
timer.refresh();
577+
if (timer.status !== 'settled') {
578+
if (timeout != null && timeout < this.handlerTimeoutTime) {
579+
// Reset timeout with new delay if it is less than the default
580+
timer.reset(timeout);
581+
} else {
582+
// Otherwise refresh
583+
timer.refresh();
584+
}
579585
}
586+
580587
this.logger.info(`Handling stream with method (${method})`);
581588
let handlerResult: [JSONValue | undefined, ReadableStream<Uint8Array>];
582589
const headerWriter = rpcStream.writable.getWriter();

src/errors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ class ErrorRPCStreamEnded<T> extends ErrorRPCProtocol<T> {
148148
class ErrorRPCTimedOut<T> extends ErrorRPCProtocol<T> {
149149
static description = 'RPC handler has timed out';
150150
code = JSONRPCErrorCode.RPCTimedOut;
151+
public toJSON(): JSONRPCError {
152+
const json = super.toJSON();
153+
if (typeof json === 'object' && !Array.isArray(json)) {
154+
(json as POJO).type = this.constructor.name;
155+
}
156+
return json;
157+
}
151158
}
152159

153160
class ErrorUtilsUndefinedBehaviour<T> extends ErrorRPCProtocol<T> {

src/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@ const defaultClientMiddlewareWrapper = (
278278
export {
279279
binaryToJsonMessageStream,
280280
jsonMessageToBinaryStream,
281+
timeoutMiddlewareClient,
282+
timeoutMiddlewareServer,
281283
defaultMiddleware,
282284
defaultServerMiddlewareWrapper,
283285
defaultClientMiddlewareWrapper,
284-
timeoutMiddlewareClient,
285-
timeoutMiddlewareServer,
286286
};

src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ type JSONRPCResponseError = {
110110
id: string | number | null;
111111
};
112112

113-
type ObjectEmpty = NonNullable<unknown>;
113+
/**
114+
* Used when an empty object is needed.
115+
* Defined here with a linter override to avoid a false positive.
116+
*/
117+
// eslint-disable-next-line
118+
type ObjectEmpty = {};
114119

115120
// Prevent overwriting the metadata type with `Omit<>`
116121
type JSONRPCRequestMetadata<T extends Record<string, JSONValue> = ObjectEmpty> =

src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ const standardErrors: {
278278
URIError,
279279
AggregateError,
280280
AbstractError,
281+
ErrorRPCTimedOut: errors.ErrorRPCTimedOut,
281282
};
282283

283284
/**
@@ -342,6 +343,7 @@ function toError(
342343
let e: Error;
343344
switch (eClass) {
344345
case AbstractError:
346+
case errors.ErrorRPCTimedOut:
345347
e = eClass.fromJSON(errorData);
346348
break;
347349
case AggregateError:

tests/RPC.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,70 @@ describe('RPC', () => {
926926

927927
await rpcServer.stop({ force: true });
928928
});
929+
testProp(
930+
'RPC client times out and server is able to ignore exception',
931+
[fc.string()],
932+
async (message) => {
933+
// Setup server and client communication pairs
934+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
935+
Uint8Array,
936+
Uint8Array
937+
>();
938+
const { p: ctxP, resolveP: resolveCtxP } = utils.promise<ContextTimed>();
939+
class TestMethod extends UnaryHandler {
940+
public handle = async (
941+
input: JSONValue,
942+
cancel: (reason?: any) => void,
943+
meta: Record<string, JSONValue> | undefined,
944+
ctx: ContextTimed,
945+
): Promise<JSONValue> => {
946+
const abortProm = utils.promise<never>();
947+
ctx.signal.addEventListener('abort', () => {
948+
resolveCtxP(ctx);
949+
abortProm.resolveP(ctx.signal.reason);
950+
});
951+
await abortProm.p;
952+
return input;
953+
};
954+
}
955+
// Set up a client and server with matching timeout settings
956+
const rpcServer = new RPCServer({
957+
logger,
958+
idGen,
959+
handlerTimeoutTime: 150,
960+
});
961+
await rpcServer.start({
962+
manifest: {
963+
testMethod: new TestMethod({}),
964+
},
965+
});
966+
rpcServer.handleStream({
967+
...serverPair,
968+
cancel: () => {},
969+
});
970+
971+
const rpcClient = new RPCClient({
972+
manifest: {
973+
testMethod: new UnaryCaller(),
974+
},
975+
streamFactory: async () => {
976+
return {
977+
...clientPair,
978+
cancel: () => {},
979+
};
980+
},
981+
logger,
982+
idGen,
983+
});
984+
await expect(
985+
rpcClient.methods.testMethod(message, { timer: 100 }),
986+
).resolves.toBe(message);
987+
await expect(ctxP).resolves.toHaveProperty(['timer', 'delay'], 100);
988+
989+
await rpcServer.stop({ force: true });
990+
},
991+
{ numRuns: 1 },
992+
);
929993
testProp(
930994
'RPC Serializes and Deserializes Error',
931995
[rpcTestUtils.errorArb(rpcTestUtils.errorArb())],

0 commit comments

Comments
 (0)