UNPKG

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
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