Skip to content

Commit 211c0f8

Browse files
authored
Merge pull request #86 from MatrixAI/feature-socket-errors
Gracefully handle certain `QUICSocket.send` errors
2 parents 1f6fc4b + 70df59c commit 211c0f8

File tree

6 files changed

+405
-17
lines changed

6 files changed

+405
-17
lines changed

src/QUICClient.ts

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,34 @@ class QUICClient {
247247
// the client, because the client bridges the push flow from the connection
248248
// to the socket.
249249
socket.connectionMap.set(connection.connectionId, connection);
250+
// Set up intermediate abort signal
251+
const abortController = new AbortController();
252+
const abortHandler = () => {
253+
abortController.abort(ctx.signal.reason);
254+
};
255+
if (ctx.signal.aborted) abortController.abort(ctx.signal.reason);
256+
else ctx.signal.addEventListener('abort', abortHandler);
257+
const handleEventQUICClientErrorSend = (
258+
evt: events.EventQUICClientErrorSend,
259+
) => {
260+
// @ts-ignore: the error contains `code` but not part of the type
261+
if (evt.detail.code === 'EINVAL') {
262+
abortController.abort(
263+
new errors.ErrorQUICClientInvalidArgument(undefined, {
264+
cause: evt.detail,
265+
}),
266+
);
267+
}
268+
};
269+
client.addEventListener(
270+
`${events.EventQUICClientErrorSend.name}-${connection.sendId}`,
271+
handleEventQUICClientErrorSend,
272+
);
250273
try {
251-
await connection.start(undefined, ctx);
274+
await connection.start(undefined, {
275+
timer: ctx.timer,
276+
signal: abortController.signal,
277+
});
252278
} catch (e) {
253279
socket.connectionMap.delete(connection.connectionId);
254280
socket.removeEventListener(
@@ -284,6 +310,12 @@ class QUICClient {
284310
client.handleEventQUICClientClose,
285311
);
286312
throw e;
313+
} finally {
314+
ctx.signal.removeEventListener('abort', abortHandler);
315+
client.removeEventListener(
316+
`${events.EventQUICClientErrorSend.name}-${connection.sendId}`,
317+
handleEventQUICClientErrorSend,
318+
);
287319
}
288320
address = utils.buildAddress(host_, port);
289321
logger.info(`Created ${this.name} to ${address}`);
@@ -299,6 +331,10 @@ class QUICClient {
299331
protected config: Config;
300332
protected _closed: boolean = false;
301333
protected resolveClosedP: () => void;
334+
/**
335+
* Flag used to make sure network fail warnings are only logged once per failure
336+
*/
337+
protected networkWarned: boolean = false;
302338

303339
/**
304340
* Handles `EventQUICClientError`.
@@ -458,15 +494,49 @@ class QUICClient {
458494
evt.detail.port,
459495
evt.detail.address,
460496
);
497+
this.networkWarned = false;
461498
} catch (e) {
462-
const e_ = new errors.ErrorQUICClientInternal(
463-
'Failed to send data on the QUICSocket',
464-
{
465-
data: evt.detail,
466-
cause: e,
467-
},
468-
);
469-
this.dispatchEvent(new events.EventQUICClientError({ detail: e_ }));
499+
switch (e.code) {
500+
case 'EINVAL':
501+
{
502+
this.dispatchEvent(
503+
new events.EventQUICClientErrorSend(
504+
`${events.EventQUICClientErrorSend.name}-${evt.detail.id}`,
505+
{
506+
detail: e,
507+
},
508+
),
509+
);
510+
}
511+
break;
512+
case 'ENETUNREACH':
513+
{
514+
// We consider this branch a temp failure.
515+
// For these error codes we rely on the connection's timeout to handle.
516+
if (!this.networkWarned) {
517+
this.logger.warn(
518+
`client send failed with 'ENETUNREACH', likely due to network failure`,
519+
);
520+
this.networkWarned = true;
521+
}
522+
}
523+
break;
524+
default:
525+
{
526+
this.dispatchEvent(
527+
new events.EventQUICClientError({
528+
detail: new errors.ErrorQUICClientInternal(
529+
'Failed to send data on the QUICSocket',
530+
{
531+
data: evt.detail,
532+
cause: e,
533+
},
534+
),
535+
}),
536+
);
537+
}
538+
break;
539+
}
470540
}
471541
};
472542

src/QUICConnection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ class QUICConnection {
6262
*/
6363
public readonly streamMap: Map<StreamId, QUICStream> = new Map();
6464

65+
/**
66+
* Unique id used to identify events intended for this connection.
67+
*/
68+
public readonly sendId: string;
69+
6570
protected logger: Logger;
6671
protected socket: QUICSocket;
6772
protected config: QUICConfig;
@@ -322,6 +327,7 @@ class QUICConnection {
322327
logger?: Logger;
323328
}) {
324329
this.logger = logger ?? new Logger(`${this.constructor.name} ${scid}`);
330+
this.sendId = scid.toString();
325331
if (
326332
config.keepAliveIntervalTime != null &&
327333
config.maxIdleTimeout !== 0 &&
@@ -876,6 +882,7 @@ class QUICConnection {
876882
this.dispatchEvent(
877883
new events.EventQUICConnectionSend({
878884
detail: {
885+
id: this.sendId,
879886
msg: sendBuffer.subarray(0, sendLength),
880887
port: sendInfo.to.port,
881888
address: sendInfo.to.host,

src/QUICServer.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class QUICServer {
6262
protected _closed: boolean = false;
6363
protected _closedP: Promise<void>;
6464
protected resolveClosedP: () => void;
65+
/**
66+
* Flag used to make sure network fail warnings are only logged once per failure
67+
*/
68+
protected networkWarned: boolean = false;
6569

6670
/**
6771
* Handles `EventQUICServerError`.
@@ -195,15 +199,49 @@ class QUICServer {
195199
evt.detail.port,
196200
evt.detail.address,
197201
);
202+
this.networkWarned = false;
198203
} catch (e) {
199-
const e_ = new errors.ErrorQUICServerInternal(
200-
'Failed to send data on the QUICSocket',
201-
{
202-
data: evt.detail,
203-
cause: e,
204-
},
205-
);
206-
this.dispatchEvent(new events.EventQUICServerError({ detail: e_ }));
204+
switch (e.code) {
205+
case 'EINVAL':
206+
{
207+
this.dispatchEvent(
208+
new events.EventQUICClientErrorSend(
209+
`${events.EventQUICClientErrorSend.name}-${evt.detail.id}`,
210+
{
211+
detail: e,
212+
},
213+
),
214+
);
215+
}
216+
break;
217+
case 'ENETUNREACH':
218+
{
219+
// We consider this branch a temp failure.
220+
// For these error codes we rely on the connection's timeout to handle.
221+
if (!this.networkWarned) {
222+
this.logger.warn(
223+
`server send failed with 'ENETUNREACH', likely due to network failure`,
224+
);
225+
this.networkWarned = true;
226+
}
227+
}
228+
break;
229+
default:
230+
{
231+
this.dispatchEvent(
232+
new events.EventQUICServerError({
233+
detail: new errors.ErrorQUICServerInternal(
234+
'Failed to send data on the QUICSocket',
235+
{
236+
data: evt.detail,
237+
cause: e,
238+
},
239+
),
240+
}),
241+
);
242+
}
243+
break;
244+
}
207245
}
208246
};
209247

src/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class ErrorQUICClientSocketNotRunning<T> extends ErrorQUICClient<T> {
5959
'QUIC Client cannot be created with an unstarted shared QUIC socket';
6060
}
6161

62+
class ErrorQUICClientInvalidArgument<T> extends ErrorQUICClient<T> {
63+
static description =
64+
'QUIC Client had a failure relating to an invalid argument';
65+
}
66+
6267
class ErrorQUICClientInvalidHost<T> extends ErrorQUICClient<T> {
6368
static description = 'QUIC Client cannot be created with the specified host';
6469
}
@@ -293,6 +298,7 @@ export {
293298
ErrorQUICClientDestroyed,
294299
ErrorQUICClientCreateTimeout,
295300
ErrorQUICClientSocketNotRunning,
301+
ErrorQUICClientInvalidArgument,
296302
ErrorQUICClientInvalidHost,
297303
ErrorQUICClientInternal,
298304
ErrorQUICServer,

src/events.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class EventQUICClientError extends EventQUICClient<
6262
| ErrorQUICConnectionInternal<unknown>
6363
> {}
6464

65+
class EventQUICClientErrorSend extends EventQUICSocket<Error> {}
66+
6567
class EventQUICClientClose extends EventQUICClient<
6668
| ErrorQUICClientSocketNotRunning<unknown>
6769
| ErrorQUICConnectionLocal<unknown>
@@ -126,6 +128,7 @@ class EventQUICConnectionClose extends EventQUICConnection<
126128
class EventQUICConnectionStream extends EventQUICConnection<QUICStream> {}
127129

128130
class EventQUICConnectionSend extends EventQUICConnection<{
131+
id: string;
129132
msg: Uint8Array;
130133
port: number;
131134
address: string;
@@ -193,6 +196,7 @@ export {
193196
EventQUICClientDestroy,
194197
EventQUICClientDestroyed,
195198
EventQUICClientError,
199+
EventQUICClientErrorSend,
196200
EventQUICClientClose,
197201
EventQUICServer,
198202
EventQUICServerStart,

0 commit comments

Comments
 (0)