@rivetkit/redis
Version:
_Lightweight Libraries for Backends_
198 lines (173 loc) • 5.03 kB
text/typescript
import {
generateConnId,
generateConnToken,
type Registry,
type RegistryConfig,
type RunConfig,
} from "@rivetkit/core";
import type { Client } from "@rivetkit/core/client";
import type { ActorDriver } from "@rivetkit/core/driver-helpers";
import * as errors from "@rivetkit/core/errors";
import { ActorPeer } from "./actor-peer";
import type { CoordinateDriverConfig } from "./config";
import type { CoordinateDriver } from "./driver";
import { logger } from "./log";
import {
publishMessageToLeader,
publishMessageToLeaderNoRetry,
} from "./node/message";
import type { NodeMessage } from "./node/protocol";
import type { GlobalState } from "./types";
export interface RelayConnDriver {
/** Called on disconnect (regardless of source). */
disconnect(reason?: string): Promise<void>;
}
/**
* This is different than `Connection`. `Connection` represents the data of the connection state on the actor itself, `RelayConnection` supports managing a connection for a actor running on another machine over pubsub.
*/
export class RelayConn {
#registryConfig: RegistryConfig;
#runConfig: RunConfig;
#driverConfig: CoordinateDriverConfig;
#coordinateDriver: CoordinateDriver;
#actorDriver: ActorDriver;
#inlineClient: Client<Registry<any>>;
#globalState: GlobalState;
#driver: RelayConnDriver;
#actorId: string;
#actorPeer?: ActorPeer;
#connId?: string;
#connToken?: string;
#disposed = false;
#abortController = new AbortController();
public get actorId(): string {
return this.#actorId;
}
public get connId(): string {
if (!this.#connId) throw new errors.InternalError("Missing connId");
return this.#connId;
}
public get connToken(): string {
if (!this.#connToken) throw new errors.InternalError("Missing connToken");
return this.#connToken;
}
constructor(
registryConfig: RegistryConfig,
runConfig: RunConfig,
driverConfig: CoordinateDriverConfig,
actorDriver: ActorDriver,
inlineClient: Client<Registry<any>>,
coordinateDriver: CoordinateDriver,
globalState: GlobalState,
driver: RelayConnDriver,
actorId: string,
) {
this.#registryConfig = registryConfig;
this.#runConfig = runConfig;
this.#driverConfig = driverConfig;
this.#coordinateDriver = coordinateDriver;
this.#actorDriver = actorDriver;
this.#inlineClient = inlineClient;
this.#driver = driver;
this.#globalState = globalState;
this.#actorId = actorId;
}
async start() {
// TODO: Handle errors gracefully
// Add connection
const connId = generateConnId();
const connToken = generateConnToken();
this.#connId = connId;
this.#connToken = connToken;
logger().debug("starting relay connection", {
actorId: this.#actorId,
connId: this.#connId,
});
// Create actor peer
this.#actorPeer = await ActorPeer.acquire(
this.#registryConfig,
this.#runConfig,
this.#driverConfig,
this.#actorDriver,
this.#inlineClient,
this.#coordinateDriver,
this.#globalState,
this.#actorId,
connId,
);
this.#globalState.relayConns.set(connId, this);
}
async publishMessageToleader(message: NodeMessage, retry: boolean) {
if (this.#disposed) {
logger().warn(
"attempted to call sendMessageToLeader on disposed RelayConn",
);
return;
}
if (retry) {
await publishMessageToLeader(
this.#registryConfig,
this.#driverConfig,
this.#coordinateDriver,
this.#globalState,
this.#actorId,
message,
this.#abortController.signal,
);
} else {
await publishMessageToLeaderNoRetry(
this.#registryConfig,
this.#driverConfig,
this.#coordinateDriver,
this.#globalState,
this.#actorId,
message,
this.#abortController.signal,
);
}
}
/**
* Closes the connection and cleans it up.
*
* @param fromLeader - If this message is coming from the leader. This will prevent sending a close message back to the leader.
*/
async disconnect(
fromLeader: boolean,
reason: string | undefined,
disconnectMessageToleader: NodeMessage | undefined,
) {
if (this.#disposed) {
logger().warn("attempted to call disconnect on disposed RelayConn");
return;
}
this.#disposed = true;
this.#abortController.abort();
// Disconnect driver
await this.#driver.disconnect(reason);
// Clean up state
if (this.#connId) {
// Remove connection
this.#globalState.relayConns.delete(this.#connId);
// Publish connection close
if (!fromLeader && this.#actorPeer?.leaderNodeId) {
if (disconnectMessageToleader) {
await publishMessageToLeader(
this.#registryConfig,
this.#driverConfig,
this.#coordinateDriver,
this.#globalState,
this.#actorId,
disconnectMessageToleader,
undefined,
);
}
}
// Remove reference to actor (will shut down if no more references)
//
// IMPORTANT: Do this last since we need to send the connection close event
await this.#actorPeer?.removeConnectionReference(this.#connId);
} else {
logger().warn("disposing connection without connection id");
}
}
}