inngest
Version:
Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.
234 lines (232 loc) • 10 kB
JavaScript
import { ConnectMessage, GatewayMessageType, WorkerRequestAckData, WorkerRequestExtendLeaseAckData, WorkerRequestExtendLeaseData } from "../../../../proto/src/components/connect/protobuf/connect.js";
import { ensureUnsharedArrayBuffer } from "../../buffer.js";
import { ConnectionState } from "../../types.js";
import { parseGatewayExecutorRequest, parseWorkerReplyAck } from "../../messages.js";
import { WAKE_REASON } from "./types.js";
//#region src/components/connect/strategies/core/requestProcessor.ts
function toError(value) {
if (value instanceof Error) return value;
return new Error(String(value));
}
var RequestProcessor = class {
constructor(accessor, wakeSignal, callbacks, logger) {
this.accessor = accessor;
this.wakeSignal = wakeSignal;
this.callbacks = callbacks;
this.logger = logger;
}
/** Handle an incoming executor request. */
async handleExecutorRequest(connectMessage, conn) {
if (this.callbacks.getState() !== ConnectionState.ACTIVE) {
this.logger.warn({ connectionId: conn.id }, "Received request while not active, skipping");
return;
}
const gatewayExecutorRequest = parseGatewayExecutorRequest(connectMessage.payload);
this.logger.debug({
requestId: gatewayExecutorRequest.requestId,
appId: gatewayExecutorRequest.appId,
appName: gatewayExecutorRequest.appName,
functionSlug: gatewayExecutorRequest.functionSlug,
stepId: gatewayExecutorRequest.stepId,
connectionId: conn.id
}, "Received gateway executor request");
if (typeof gatewayExecutorRequest.appName !== "string" || gatewayExecutorRequest.appName.length === 0) {
this.logger.warn({
requestId: gatewayExecutorRequest.requestId,
appId: gatewayExecutorRequest.appId,
functionSlug: gatewayExecutorRequest.functionSlug,
stepId: gatewayExecutorRequest.stepId,
connectionId: conn.id
}, "No app name in request, skipping");
return;
}
if (!this.accessor.appIds.includes(gatewayExecutorRequest.appName)) {
this.logger.warn({
requestId: gatewayExecutorRequest.requestId,
appId: gatewayExecutorRequest.appId,
appName: gatewayExecutorRequest.appName,
functionSlug: gatewayExecutorRequest.functionSlug,
stepId: gatewayExecutorRequest.stepId,
connectionId: conn.id
}, "No request handler found for app, skipping");
return;
}
conn.ws.send(ensureUnsharedArrayBuffer(ConnectMessage.encode(ConnectMessage.create({
kind: GatewayMessageType.WORKER_REQUEST_ACK,
payload: WorkerRequestAckData.encode(WorkerRequestAckData.create({
accountId: gatewayExecutorRequest.accountId,
envId: gatewayExecutorRequest.envId,
appId: gatewayExecutorRequest.appId,
functionSlug: gatewayExecutorRequest.functionSlug,
requestId: gatewayExecutorRequest.requestId,
stepId: gatewayExecutorRequest.stepId,
userTraceCtx: gatewayExecutorRequest.userTraceCtx,
systemTraceCtx: gatewayExecutorRequest.systemTraceCtx,
runId: gatewayExecutorRequest.runId
})).finish()
})).finish()));
this.accessor.inProgressRequests.wg.add(1);
this.accessor.inProgressRequests.requestLeases[gatewayExecutorRequest.requestId] = gatewayExecutorRequest.leaseId;
const leaseAcquiredAt = Date.now();
this.accessor.inProgressRequests.requestMeta[gatewayExecutorRequest.requestId] = {
requestId: gatewayExecutorRequest.requestId,
runId: gatewayExecutorRequest.runId,
stepId: gatewayExecutorRequest.stepId,
appId: gatewayExecutorRequest.appId,
envId: gatewayExecutorRequest.envId,
functionSlug: gatewayExecutorRequest.functionSlug,
accountId: gatewayExecutorRequest.accountId,
leaseAcquiredAt,
leaseLastExtendedAt: leaseAcquiredAt
};
const inFlightCount = Object.keys(this.accessor.inProgressRequests.requestLeases).length;
this.logger.debug({
requestId: gatewayExecutorRequest.requestId,
functionSlug: gatewayExecutorRequest.functionSlug,
inFlightCount
}, "Request acknowledged");
const startedAt = Date.now();
const originalWs = conn.ws;
const originalConnectionId = conn.id;
let extendLeaseInterval;
extendLeaseInterval = setInterval(() => {
const currentLeaseId = this.accessor.inProgressRequests.requestLeases[gatewayExecutorRequest.requestId];
if (!currentLeaseId) {
clearInterval(extendLeaseInterval);
return;
}
const latestConn = {
ws: this.accessor.activeConnection?.ws ?? originalWs,
id: this.accessor.activeConnection?.id ?? originalConnectionId
};
this.logger.debug({
connectionId: latestConn.id,
leaseId: currentLeaseId,
requestId: gatewayExecutorRequest.requestId,
functionSlug: gatewayExecutorRequest.functionSlug,
runId: gatewayExecutorRequest.runId,
stepId: gatewayExecutorRequest.stepId
}, "Extending lease");
if (latestConn.ws.readyState !== WebSocket.OPEN) {
this.logger.warn({
connectionId: latestConn.id,
requestId: gatewayExecutorRequest.requestId
}, "Cannot extend lease, no open WebSocket available");
return;
}
try {
latestConn.ws.send(ensureUnsharedArrayBuffer(ConnectMessage.encode(ConnectMessage.create({
kind: GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE,
payload: WorkerRequestExtendLeaseData.encode(WorkerRequestExtendLeaseData.create({
accountId: gatewayExecutorRequest.accountId,
envId: gatewayExecutorRequest.envId,
appId: gatewayExecutorRequest.appId,
functionSlug: gatewayExecutorRequest.functionSlug,
requestId: gatewayExecutorRequest.requestId,
stepId: gatewayExecutorRequest.stepId,
runId: gatewayExecutorRequest.runId,
userTraceCtx: gatewayExecutorRequest.userTraceCtx,
systemTraceCtx: gatewayExecutorRequest.systemTraceCtx,
leaseId: currentLeaseId
})).finish()
})).finish()));
const meta = this.accessor.inProgressRequests.requestMeta[gatewayExecutorRequest.requestId];
if (meta) meta.leaseLastExtendedAt = Date.now();
} catch (err) {
this.logger.warn({
connectionId: latestConn.id,
requestId: gatewayExecutorRequest.requestId,
err: toError(err)
}, "Failed to send lease extension");
}
}, conn.extendLeaseIntervalMs);
try {
const responseBytes = await this.callbacks.handleExecutionRequest(gatewayExecutorRequest);
const durationMs = Date.now() - startedAt;
this.logger.debug({
requestId: gatewayExecutorRequest.requestId,
functionSlug: gatewayExecutorRequest.functionSlug,
durationMs
}, "Request execution completed");
if (!this.accessor.activeConnection) {
this.logger.warn({ requestId: gatewayExecutorRequest.requestId }, "No current WebSocket, buffering response");
if (this.callbacks.onBufferResponse) this.callbacks.onBufferResponse(gatewayExecutorRequest.requestId, responseBytes);
return;
}
this.logger.debug({
connectionId: this.accessor.activeConnection.id,
requestId: gatewayExecutorRequest.requestId
}, "Sending worker reply");
this.accessor.activeConnection.ws.send(ensureUnsharedArrayBuffer(ConnectMessage.encode(ConnectMessage.create({
kind: GatewayMessageType.WORKER_REPLY,
payload: responseBytes
})).finish()));
} catch (err) {
const durationMs = Date.now() - startedAt;
this.logger.warn({
requestId: gatewayExecutorRequest.requestId,
durationMs,
err: toError(err)
}, "Execution error");
} finally {
this.accessor.inProgressRequests.wg.done();
delete this.accessor.inProgressRequests.requestLeases[gatewayExecutorRequest.requestId];
delete this.accessor.inProgressRequests.requestMeta[gatewayExecutorRequest.requestId];
clearInterval(extendLeaseInterval);
const remainingInFlight = Object.keys(this.accessor.inProgressRequests.requestLeases).length;
this.logger.debug({
requestId: gatewayExecutorRequest.requestId,
remainingInFlight
}, "Request finished");
if (this.accessor.shutdownRequested && !this.hasInFlightRequests()) this.wakeSignal.wake(WAKE_REASON.RequestFinishedOnShutdown);
}
}
/** Handle a reply ACK from the gateway. */
handleReplyAck(connectMessage, connectionId) {
const replyAck = parseWorkerReplyAck(connectMessage.payload);
this.logger.debug({
connectionId,
requestId: replyAck.requestId
}, "Acknowledging reply ack");
this.callbacks.onReplyAck?.(replyAck.requestId);
}
/** Handle a lease extension ACK from the gateway. */
handleExtendLeaseAck(connectMessage, connectionId) {
const extendLeaseAck = WorkerRequestExtendLeaseAckData.decode(connectMessage.payload);
const hasLease = Object.hasOwn(this.accessor.inProgressRequests.requestLeases, extendLeaseAck.requestId);
const meta = this.accessor.inProgressRequests.requestMeta[extendLeaseAck.requestId];
if (!hasLease || !meta) {
this.logger.debug({
connectionId,
requestId: extendLeaseAck.requestId,
newLeaseId: extendLeaseAck.newLeaseId,
hadLease: hasLease,
hadMeta: !!meta
}, "Ignoring extend lease ack for non-in-flight request");
return;
}
this.logger.debug({
connectionId,
newLeaseId: extendLeaseAck.newLeaseId
}, "Received extend lease ack");
if (extendLeaseAck.newLeaseId) this.accessor.inProgressRequests.requestLeases[extendLeaseAck.requestId] = extendLeaseAck.newLeaseId;
else {
this.logger.error({
connectionId,
requestId: extendLeaseAck.requestId,
functionSlug: meta?.functionSlug,
runId: meta?.runId,
stepId: meta?.stepId
}, "Lease lost: the server did not renew the lease for this request. Another worker may have claimed it. The in-progress execution will continue but its result may be discarded.");
delete this.accessor.inProgressRequests.requestLeases[extendLeaseAck.requestId];
delete this.accessor.inProgressRequests.requestMeta[extendLeaseAck.requestId];
if (this.accessor.shutdownRequested && !this.hasInFlightRequests()) this.wakeSignal.wake(WAKE_REASON.LeaseLostOnShutdown);
}
}
hasInFlightRequests() {
return Object.keys(this.accessor.inProgressRequests.requestLeases).length > 0;
}
};
//#endregion
export { RequestProcessor };
//# sourceMappingURL=requestProcessor.js.map