Skip to content

Commit 9c16b5a

Browse files
tegefaulkesCMCDragonkai
authored andcommitted
feat: allow force stopping a QUICSocket
This will allow easy cleanup after tests to ensure the process doesn't hold open. Everything else can be handled via garbage collection. I removed the register client method, It's not needed since we check against the connection map before stopping. * Related #14 [ci skip]
1 parent 5fcdba4 commit 9c16b5a

File tree

3 files changed

+61
-40
lines changed

3 files changed

+61
-40
lines changed

src/QUICClient.ts

-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,6 @@ class QUICClient extends EventTarget {
266266
this.socket = socket;
267267
this.isSocketShared = isSocketShared;
268268
// Registers itself to the socket
269-
this.socket.registerClient(this);
270269
if (!isSocketShared) {
271270
this.socket.addEventListener('error', this.handleQUICSocketError);
272271
}

src/QUICSocket.ts

+16-32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type QUICClient from './QUICClient';
21
import type QUICServer from './QUICServer';
32
import type QUICConnection from './QUICConnection';
43
import type { Crypto, Host, Hostname, Port } from './types';
@@ -50,12 +49,12 @@ class QUICSocket extends EventTarget {
5049
* If it is non-QUIC, we can discard the data.
5150
* If there are multiple coalesced QUIC packets, it is expected that
5251
* all packets are intended for the same connection. This means we only
53-
* need to parse the first QUIC packet to determinine what connection to route
52+
* need to parse the first QUIC packet to determining what connection to route
5453
* the data to.
5554
*/
5655
protected handleSocketMessage = async (
5756
data: Buffer,
58-
rinfo: dgram.RemoteInfo,
57+
remoteInfo: dgram.RemoteInfo,
5958
) => {
6059
// The data buffer may have multiple coalesced QUIC packets.
6160
// This header is parsed from the first packet.
@@ -92,9 +91,9 @@ class QUICSocket extends EventTarget {
9291
quiche.MAX_CONN_ID_LEN,
9392
);
9493

95-
const remoteInfo = {
96-
host: rinfo.address as Host,
97-
port: rinfo.port as Port,
94+
const remoteInfo_ = {
95+
host: remoteInfo.address as Host,
96+
port: remoteInfo.port as Port,
9897
};
9998

10099
// Now both must be checked
@@ -107,7 +106,7 @@ class QUICSocket extends EventTarget {
107106
}
108107
const conn_ = await this.server.connectionNew(
109108
data,
110-
remoteInfo,
109+
remoteInfo_,
111110
header,
112111
dcid,
113112
scid,
@@ -126,7 +125,7 @@ class QUICSocket extends EventTarget {
126125
// When we register a client, we have to put the connection in our
127126
// connection map
128127
}
129-
await conn.recv(data, remoteInfo);
128+
await conn.recv(data, remoteInfo_);
130129

131130
// The `conn.recv` now may actually destroy the connection
132131
// In that sense, there's nothing to send
@@ -273,10 +272,16 @@ class QUICSocket extends EventTarget {
273272
this.logger.info(`Started ${this.constructor.name} on ${address}`);
274273
}
275274

276-
public async stop(): Promise<void> {
275+
/**
276+
* Will stop the socket.
277+
* An `ErrorQUICSocketConnectionsActive` will be thrown if there are active connections.
278+
* If force is true, it will skip checking connections and stop the socket.
279+
* @param force - Will force the socket to end even if there are active connections, used for cleaning up after tests.
280+
*/
281+
public async stop(force = false): Promise<void> {
277282
const address = utils.buildAddress(this._host, this._port);
278283
this.logger.info(`Stop ${this.constructor.name} on ${address}`);
279-
if (this.connectionMap.size > 0) {
284+
if (!force && this.connectionMap.size > 0) {
280285
throw new errors.ErrorQUICSocketConnectionsActive(
281286
`Cannot stop QUICSocket with ${this.connectionMap.size} active connection(s)`,
282287
);
@@ -354,27 +359,6 @@ class QUICSocket extends EventTarget {
354359
return this.socketSend(...params);
355360
}
356361

357-
/**
358-
* Registers a client to the socket
359-
* This is a new client, but clients don't die by itself?
360-
*/
361-
public registerClient(client: QUICClient) {
362-
// So what really this does?
363-
// Is this about creating a connection?
364-
// So we can add the connection to the map?
365-
// And if we are doing
366-
// QUICConnection.createQUICConnection
367-
// Then that means, we are really creating that connection in the async creator
368-
// That means the async creator needs to create teh `connection` and call it too
369-
this.logger.error('registerClient IS NOT IMPLEMENTED!');
370-
}
371-
372-
// But we already have a connection map
373-
// well yea, we are checking liveness of connections
374-
// But client destruction is only way to destory connections
375-
// But if the client connection fails
376-
// we need to simultaneously destroy the client
377-
378362
/**
379363
* Sets a single server to the socket
380364
* You can only have 1 server for the socket
@@ -388,7 +372,7 @@ class QUICSocket extends EventTarget {
388372
* Just go straight to calling a thing
389373
* We can call this.server.handleConnection()
390374
* Why `handleConnection` because technically it's built on top of the handleMessage
391-
* Thatbecomes the key idea there
375+
* That becomes the key idea there
392376
* handleNewConnection
393377
* And all sorts of other stuff!
394378
* Or whatever it needs to be

tests/QUICSocket.test.ts

+45-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import type { Crypto, Host, Hostname, Port } from '@/types';
1+
import type { Crypto, Host } from '@/types';
2+
import type QUICConnection from '@/QUICConnection';
23
import dgram from 'dgram';
34
import Logger, { LogLevel, StreamHandler } from '@matrixai/logger';
45
import QUICSocket from '@/QUICSocket';
56
import * as utils from '@/utils';
67
import * as errors from '@/errors';
8+
import QUICConnectionId from '@/QUICConnectionId';
79
import * as testsUtils from './utils';
810

911
describe(QUICSocket.name, () => {
@@ -19,7 +21,7 @@ describe(QUICSocket.name, () => {
1921
let ipv6Socket: dgram.Socket;
2022
let dualStackSocket: dgram.Socket;
2123
let ipv4SocketBind: (port: number, host: string) => Promise<void>;
22-
let ipv4SocketSend: (...params: Array<any>) => Promise<number>;
24+
let _ipv4SocketSend: (...params: Array<any>) => Promise<number>;
2325
let ipv4SocketClose: () => Promise<void>;
2426
let ipv4SocketPort: number;
2527
// Handle IPv4 messages
@@ -32,7 +34,7 @@ describe(QUICSocket.name, () => {
3234
ipv4SocketMessageResolveP = resolveP;
3335
};
3436
let ipv6SocketBind: (port: number, host: string) => Promise<void>;
35-
let ipv6SocketSend: (...params: Array<any>) => Promise<number>;
37+
let _ipv6SocketSend: (...params: Array<any>) => Promise<number>;
3638
let ipv6SocketClose: () => Promise<void>;
3739
let ipv6SocketPort: number;
3840
// Handle IPv6 messages
@@ -45,7 +47,7 @@ describe(QUICSocket.name, () => {
4547
ipv6SocketMessageResolveP = resolveP;
4648
};
4749
let dualStackSocketBind: (port: number, host: string) => Promise<void>;
48-
let dualStackSocketSend: (...params: Array<any>) => Promise<number>;
50+
let _dualStackSocketSend: (...params: Array<any>) => Promise<number>;
4951
let dualStackSocketClose: () => Promise<void>;
5052
let dualStackSocketPort: number;
5153
// Handle dual stack messages
@@ -81,15 +83,15 @@ describe(QUICSocket.name, () => {
8183
ipv6Only: false,
8284
});
8385
ipv4SocketBind = utils.promisify(ipv4Socket.bind).bind(ipv4Socket);
84-
ipv4SocketSend = utils.promisify(ipv4Socket.send).bind(ipv4Socket);
86+
_ipv4SocketSend = utils.promisify(ipv4Socket.send).bind(ipv4Socket);
8587
ipv4SocketClose = utils.promisify(ipv4Socket.close).bind(ipv4Socket);
8688
ipv6SocketBind = utils.promisify(ipv6Socket.bind).bind(ipv6Socket);
87-
ipv6SocketSend = utils.promisify(ipv6Socket.send).bind(ipv6Socket);
89+
_ipv6SocketSend = utils.promisify(ipv6Socket.send).bind(ipv6Socket);
8890
ipv6SocketClose = utils.promisify(ipv6Socket.close).bind(ipv6Socket);
8991
dualStackSocketBind = utils
9092
.promisify(dualStackSocket.bind)
9193
.bind(dualStackSocket);
92-
dualStackSocketSend = utils
94+
_dualStackSocketSend = utils
9395
.promisify(dualStackSocket.send)
9496
.bind(dualStackSocket);
9597
dualStackSocketClose = utils
@@ -506,4 +508,40 @@ describe(QUICSocket.name, () => {
506508
]);
507509
});
508510
});
511+
test('socket should throw if stopped with active connections', async () => {
512+
const socket = new QUICSocket({
513+
crypto,
514+
logger,
515+
});
516+
await socket.start({
517+
host: '127.0.0.1' as Host,
518+
});
519+
const connectionId = QUICConnectionId.fromBuffer(
520+
Buffer.from('SomeRandomId'),
521+
);
522+
socket.connectionMap.set(connectionId, {
523+
type: 'client',
524+
} as QUICConnection);
525+
await expect(socket.stop()).rejects.toThrow(
526+
errors.ErrorQUICSocketConnectionsActive,
527+
);
528+
socket.connectionMap.delete(connectionId);
529+
await expect(socket.stop()).toResolve();
530+
});
531+
test('socket should stop when forced with active connections', async () => {
532+
const socket = new QUICSocket({
533+
crypto,
534+
logger,
535+
});
536+
await socket.start({
537+
host: '127.0.0.1' as Host,
538+
});
539+
const connectionId = QUICConnectionId.fromBuffer(
540+
Buffer.from('SomeRandomId'),
541+
);
542+
socket.connectionMap.set(connectionId, {
543+
type: 'client',
544+
} as QUICConnection);
545+
await expect(socket.stop(true)).toResolve();
546+
});
509547
});

0 commit comments

Comments
 (0)