Skip to content

Commit

Permalink
feat: added keepalive to connections
Browse files Browse the repository at this point in the history
* Added a `keepAliveDelay` parameter to client, server and connections.
* Connections will send a ping frame after each interval, this should reset the timeout for both sides, given that the ping is received and responded to.
* Added tests for this feature.

* Fixes #4

[ci skip]
  • Loading branch information
tegefaulkes committed May 11, 2023
1 parent e325865 commit d542dcb
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 3 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export class Connection {
* https://stackoverflow.com/q/50343130/582917
*/
pathStats(): Array<PathStats>
sendAckEliciting(): void
}
export class StreamIter {
[Symbol.iterator](): Iterator<number, void, void>
Expand Down
6 changes: 4 additions & 2 deletions src/QUICClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class QUICClient extends EventTarget {
codeToReason,
maxReadableStreamBytes,
maxWritableStreamBytes,
keepaliveIntervalTime,
logger = new Logger(`${this.name}`),
config = {},
}: {
Expand All @@ -74,6 +75,7 @@ class QUICClient extends EventTarget {
codeToReason?: StreamCodeToReason;
maxReadableStreamBytes?: number;
maxWritableStreamBytes?: number;
keepaliveIntervalTime?: number;
logger?: Logger;
config?: Partial<QUICConfig>;
}) {
Expand Down Expand Up @@ -199,9 +201,9 @@ class QUICClient extends EventTarget {
socket.removeEventListener('error', handleQUICSocketError);
// Remove the temporary connection error handler
connection.removeEventListener('error', handleConnectionError);

// Setting up keep alive
connection.setKeepAlive(keepaliveIntervalTime);
// Now we create the client

const client = new QUICClient({
crypto,
socket,
Expand Down
27 changes: 27 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 @@ -330,6 +331,11 @@ class QUICConnection extends EventTarget {
force?: boolean;
} = {}) {
this.logger.info(`Destroy ${this.constructor.name}`);
// Clean up keep alive
if (this.keepAliveInterval != null) {
clearTimeout(this.keepAliveInterval);
delete this.keepAliveInterval;
}
// Handle destruction concurrently
const destroyProms: Array<Promise<void>> = [];
for (const stream of this.streamMap.values()) {
Expand Down Expand Up @@ -694,6 +700,27 @@ class QUICConnection extends EventTarget {
});
}

/**
* Used to update or disable the keep alive interval.
* Calling this will reset the delay before the next keep alive.
*/
@ready(new errors.ErrorQUICConnectionDestroyed())
public setKeepAlive(intervalDelay?: number) {
// Clearing timeout prior to update
if (this.keepAliveInterval != null) {
clearTimeout(this.keepAliveInterval);
delete this.keepAliveInterval;
}
// Setting up keep alive interval
if (intervalDelay != null) {
this.keepAliveInterval = setInterval(async () => {
// Trigger an ping frame and send
this.conn.sendAckEliciting();
await this.send();
}, intervalDelay);
}
}

// Timeout handling, these methods handle time keeping for quiche.
// Quiche will request an amount of time, We then call `onTimeout()` after that time has passed.
protected deadline: number = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/QUICServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class QUICServer extends EventTarget {
protected codeToReason: StreamCodeToReason | undefined;
protected maxReadableStreamBytes?: number | undefined;
protected maxWritableStreamBytes?: number | undefined;
protected keepaliveIntervalTime?: number | undefined;
protected connectionMap: QUICConnectionMap;

/**
Expand All @@ -75,6 +76,7 @@ class QUICServer extends EventTarget {
codeToReason,
maxReadableStreamBytes,
maxWritableStreamBytes,
keepaliveIntervalTime,
logger,
}: {
crypto: {
Expand All @@ -91,6 +93,7 @@ class QUICServer extends EventTarget {
codeToReason?: StreamCodeToReason;
maxReadableStreamBytes?: number;
maxWritableStreamBytes?: number;
keepaliveIntervalTime?: number;
logger?: Logger;
}) {
super();
Expand Down Expand Up @@ -120,6 +123,7 @@ class QUICServer extends EventTarget {
this.codeToReason = codeToReason;
this.maxReadableStreamBytes = maxReadableStreamBytes;
this.maxWritableStreamBytes = maxWritableStreamBytes;
this.keepaliveIntervalTime = keepaliveIntervalTime;
}

@ready(new errors.ErrorQUICServerNotRunning())
Expand Down Expand Up @@ -299,6 +303,7 @@ class QUICServer extends EventTarget {
`${QUICConnection.name} ${scid.toString().slice(32)}`,
),
});
connection.setKeepAlive(this.keepaliveIntervalTime);

this.dispatchEvent(
new events.QUICServerConnectionEvent({ detail: connection }),
Expand Down
7 changes: 7 additions & 0 deletions src/native/napi/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,4 +1068,11 @@ impl Connection {
|s| s.into()
).collect();
}

#[napi]
pub fn send_ack_eliciting(&mut self) -> napi::Result<()> {
return self.0.send_ack_eliciting().or_else(
|err| Err(Error::from_reason(err.to_string()))
);
}
}
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
Loading

0 comments on commit d542dcb

Please sign in to comment.