Skip to content

Commit

Permalink
wip: implementing keepalive
Browse files Browse the repository at this point in the history
* Done adding the feature,
* Still need to write tests

* Related #6

[ci skip]
  • Loading branch information
tegefaulkes committed May 10, 2023
1 parent e325865 commit de5eb7b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
19 changes: 19 additions & 0 deletions src/QUICConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class QUICConnection extends EventTarget {
protected logger: Logger;
protected socket: QUICSocket;
protected timer?: ReturnType<typeof setTimeout>;
protected keepAliveInterval?: ReturnType<typeof setInterval>;
public readonly closedP: Promise<void>;
protected resolveCloseP?: () => void;

Expand Down Expand Up @@ -107,6 +108,7 @@ class QUICConnection extends EventTarget {
new Error(`${type.toString()} ${code.toString()}`),
maxReadableStreamBytes,
maxWritableStreamBytes,
keepAliveDelay,
logger = new Logger(`${this.name} ${scid}`),
}: {
scid: QUICConnectionId;
Expand All @@ -117,6 +119,7 @@ class QUICConnection extends EventTarget {
codeToReason?: StreamCodeToReason;
maxReadableStreamBytes?: number;
maxWritableStreamBytes?: number;
keepAliveDelay?: number;
logger?: Logger;
}) {
logger.info(`Connect ${this.name}`);
Expand Down Expand Up @@ -148,6 +151,7 @@ class QUICConnection extends EventTarget {
codeToReason,
maxReadableStreamBytes,
maxWritableStreamBytes,
keepAliveDelay,
logger,
});
socket.connectionMap.set(connection.connectionId, connection);
Expand All @@ -169,6 +173,7 @@ class QUICConnection extends EventTarget {
new Error(`${type.toString()} ${code.toString()}`),
maxReadableStreamBytes,
maxWritableStreamBytes,
keepAliveDelay,
logger = new Logger(`${this.name} ${scid}`),
}: {
scid: QUICConnectionId;
Expand All @@ -180,6 +185,7 @@ class QUICConnection extends EventTarget {
codeToReason?: StreamCodeToReason;
maxReadableStreamBytes?: number;
maxWritableStreamBytes?: number;
keepAliveDelay?: number;
logger?: Logger;
}): Promise<QUICConnection> {
logger.info(`Accept ${this.name}`);
Expand Down Expand Up @@ -211,6 +217,7 @@ class QUICConnection extends EventTarget {
codeToReason,
maxReadableStreamBytes,
maxWritableStreamBytes,
keepAliveDelay,
logger,
});
socket.connectionMap.set(connection.connectionId, connection);
Expand All @@ -228,6 +235,7 @@ class QUICConnection extends EventTarget {
codeToReason,
maxReadableStreamBytes,
maxWritableStreamBytes,
keepAliveDelay,
logger,
}: {
type: 'client' | 'server';
Expand All @@ -239,6 +247,7 @@ class QUICConnection extends EventTarget {
codeToReason: StreamCodeToReason;
maxReadableStreamBytes: number | undefined;
maxWritableStreamBytes: number | undefined;
keepAliveDelay: number | undefined;
logger: Logger;
}) {
super();
Expand Down Expand Up @@ -275,6 +284,14 @@ class QUICConnection extends EventTarget {
const { p: handshakeP, resolveP: resolveHandshakeP } = utils.promise();
this.handshakeP = handshakeP;
this.resolveHandshakeP = resolveHandshakeP;
// Setting up keep alive interval
if (keepAliveDelay != null) {
this.keepAliveInterval = setTimeout(async () => {
// Trigger an ping frame and send
this.conn.sendAckEliciting();
await this.send();
}, keepAliveDelay);
}
}

// Immediately call this after construction
Expand Down Expand Up @@ -330,6 +347,8 @@ class QUICConnection extends EventTarget {
force?: boolean;
} = {}) {
this.logger.info(`Destroy ${this.constructor.name}`);
// Clean up keep alive
if (this.keepAliveInterval != null) clearTimeout(this.keepAliveInterval);
// Handle destruction concurrently
const destroyProms: Array<Promise<void>> = [];
for (const stream of this.streamMap.values()) {
Expand Down
5 changes: 5 additions & 0 deletions src/native/napi/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,4 +1068,9 @@ impl Connection {
|s| s.into()
).collect();
}

#[napi]
pub fn send_ack_eliciting(&self) -> () {
self.0.send_ack_eliciting();
}
}
1 change: 1 addition & 0 deletions src/native/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ interface Connection {
localError(): ConnectionError | null;
stats(): Stats;
pathStats(): Array<PathStats>;
sendAckEliciting(): void;
}

interface ConnectionConstructor {
Expand Down
75 changes: 74 additions & 1 deletion tests/QUICClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Crypto, Host, Port } from '@/types';
import type * as events from '@/events';
import type QUICConnection from '@/QUICConnection';
import Logger, { LogLevel, StreamHandler, formatting } from '@matrixai/logger';
import { fc, testProp } from '@fast-check/jest';
import { destroyed } from '@matrixai/async-init';
import QUICClient from '@/QUICClient';
import QUICServer from '@/QUICServer';
import * as errors from '@/errors';
Expand All @@ -10,9 +12,10 @@ import QUICSocket from '@/QUICSocket';
import * as testsUtils from './utils';
import { tlsConfigWithCaArb } from './tlsUtils';
import { sleep } from './utils';
import * as fixtures from './fixtures/certFixtures';

describe(QUICClient.name, () => {
const logger = new Logger(`${QUICClient.name} Test`, LogLevel.WARN, [
const logger = new Logger(`${QUICClient.name} Test`, LogLevel.DEBUG, [
new StreamHandler(
formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`,
),
Expand Down Expand Up @@ -1086,4 +1089,74 @@ describe(QUICClient.name, () => {
{ numRuns: 1 },
);
});
describe('keepalive', () => {
const tlsConfig = fixtures.tlsConfigMemRSA1;
test('connection can time out', async () => {
const connectionEventProm = promise<QUICConnection>();
const server = new QUICServer({
crypto,
logger: logger.getChild(QUICServer.name),
config: {
tlsConfig,
verifyPeer: false,
maxIdleTimeout: 100,
},
});
server.addEventListener(
'connection',
(e: events.QUICServerConnectionEvent) =>
connectionEventProm.resolveP(e.detail),
);
await server.start({
host: '127.0.0.1' as Host,
});
const client = await QUICClient.createQUICClient({
host: '::ffff:127.0.0.1' as Host,
port: server.port,
localHost: '::' as Host,
crypto,
logger: logger.getChild(QUICClient.name),
config: {
verifyPeer: false,
maxIdleTimeout: 100,
},
});
// Setting no keepalive should cause the connection to time out
// It has cleaned up due to timeout
const clientConnection = client.connection;
const clientTimeoutProm = promise<void>();
clientConnection.addEventListener(
'error',
(event: events.QUICConnectionErrorEvent) => {
console.log(event.detail);
if (event.detail instanceof errors.ErrorQUICConnectionTimeout) {
console.log('RESOLVING');
clientTimeoutProm.resolveP();
}
},
);
console.log('waiting for client timeout');
await clientTimeoutProm.p;
expect(clientConnection[destroyed]).toBeTrue();

// Server connection should time out as well
const serverConnection = await connectionEventProm.p;
const serverTimeoutProm = promise<void>();
serverConnection.addEventListener(
'error',
(event: events.QUICConnectionErrorEvent) => {
console.log(event.detail);
if (event.detail instanceof errors.ErrorQUICConnectionTimeout) {
serverTimeoutProm.resolveP();
}
},
);
console.log('waiting for server timeout');
await serverTimeoutProm.p;
expect(serverConnection[destroyed]).toBeTrue();

await client.destroy();
await server.stop();
});
});
});

0 comments on commit de5eb7b

Please sign in to comment.