Skip to content

Commit 70df59c

Browse files
committed
fix: addressed crashing due to socket send failures
This commit addresses the send failures by taking any send specific errors and passing them back to the connection to be handled. Any errors such as bad arguments results in the connection throwing the problem proactivity. Any network failures are generally ignored and the Connections are left to time out. This allows for the network to drop for a short amount of time without failure of the connection and streams. [ci skip]
1 parent 1f6fc4b commit 70df59c

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)