Skip to content

Commit eaa517d

Browse files
authored
Merge pull request #89 from MatrixAI/feature-socket-errors
Gracefully handle certain `QUICSocket.send` errors
2 parents e8b1d8a + 6b62491 commit eaa517d

File tree

6 files changed

+398
-17
lines changed

6 files changed

+398
-17
lines changed

src/QUICClient.ts

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,47 @@ 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+
const code = evt.detail.code;
262+
switch (code) {
263+
// Thrown due to invalid arguments on linux
264+
case 'EINVAL':
265+
// Thrown due to invalid arguments on macOS
266+
// Falls through
267+
case 'EADDRNOTAVAIL':
268+
// Thrown due to invalid arguments on Win but also for network dropouts on all platforms
269+
// Falls through
270+
case 'ENETUNREACH':
271+
{
272+
abortController.abort(
273+
new errors.ErrorQUICClientInvalidArgument(undefined, {
274+
cause: evt.detail,
275+
}),
276+
);
277+
}
278+
break;
279+
default: // Do nothing
280+
}
281+
};
282+
client.addEventListener(
283+
`${events.EventQUICClientErrorSend.name}-${connection.sendId}`,
284+
handleEventQUICClientErrorSend,
285+
);
250286
try {
251-
await connection.start(undefined, ctx);
287+
await connection.start(undefined, {
288+
timer: ctx.timer,
289+
signal: abortController.signal,
290+
});
252291
} catch (e) {
253292
socket.connectionMap.delete(connection.connectionId);
254293
socket.removeEventListener(
@@ -284,6 +323,12 @@ class QUICClient {
284323
client.handleEventQUICClientClose,
285324
);
286325
throw e;
326+
} finally {
327+
ctx.signal.removeEventListener('abort', abortHandler);
328+
client.removeEventListener(
329+
`${events.EventQUICClientErrorSend.name}-${connection.sendId}`,
330+
handleEventQUICClientErrorSend,
331+
);
287332
}
288333
address = utils.buildAddress(host_, port);
289334
logger.info(`Created ${this.name} to ${address}`);
@@ -459,14 +504,42 @@ class QUICClient {
459504
evt.detail.address,
460505
);
461506
} 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_ }));
507+
switch (e.code) {
508+
// Thrown due to invalid arguments on linux
509+
case 'EINVAL':
510+
// Thrown due to invalid arguments on macOS
511+
// Falls through
512+
case 'EADDRNOTAVAIL':
513+
// Thrown due to invalid arguments on Win but also for network dropouts on all platforms
514+
// Falls through
515+
case 'ENETUNREACH':
516+
{
517+
this.dispatchEvent(
518+
new events.EventQUICClientErrorSend(
519+
`${events.EventQUICClientErrorSend.name}-${evt.detail.id}`,
520+
{
521+
detail: e,
522+
},
523+
),
524+
);
525+
}
526+
break;
527+
default:
528+
{
529+
this.dispatchEvent(
530+
new events.EventQUICClientError({
531+
detail: new errors.ErrorQUICClientInternal(
532+
'Failed to send data on the QUICSocket',
533+
{
534+
data: evt.detail,
535+
cause: e,
536+
},
537+
),
538+
}),
539+
);
540+
}
541+
break;
542+
}
470543
}
471544
};
472545

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: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,42 @@ class QUICServer {
196196
evt.detail.address,
197197
);
198198
} 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_ }));
199+
switch (e.code) {
200+
// Thrown due to invalid arguments on linux
201+
case 'EINVAL':
202+
// Thrown due to invalid arguments on macOS
203+
// Falls through
204+
case 'EADDRNOTAVAIL':
205+
// Thrown due to invalid arguments on Win but also for network dropouts on all platforms
206+
// Falls through
207+
case 'ENETUNREACH':
208+
{
209+
this.dispatchEvent(
210+
new events.EventQUICClientErrorSend(
211+
`${events.EventQUICClientErrorSend.name}-${evt.detail.id}`,
212+
{
213+
detail: e,
214+
},
215+
),
216+
);
217+
}
218+
break;
219+
default:
220+
{
221+
this.dispatchEvent(
222+
new events.EventQUICServerError({
223+
detail: new errors.ErrorQUICServerInternal(
224+
'Failed to send data on the QUICSocket',
225+
{
226+
data: evt.detail,
227+
cause: e,
228+
},
229+
),
230+
}),
231+
);
232+
}
233+
break;
234+
}
207235
}
208236
};
209237

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)