UNPKG

@kuflow/kuflow-temporal-worker

Version:

Worker library used by KuFlow SDKs and Temporal.

188 lines (152 loc) 6.02 kB
/** * The MIT License * Copyright © 2021-present KuFlow S.L. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ import type { FullOperationResponse } from '@azure/core-client' import type { Worker } from '@kuflow/kuflow-rest' import { Runtime } from '@temporalio/worker' import os from 'os' import type { KuFlowTemporalConnectionOptions } from './kuflow-temporal-connection-options' export interface KuFlowWorkerInformationNotifierBackoff { /** * Seconds between errors, default 1 second */ sleep?: number /** * Exponential rate applied if the error persists, default 2.5 */ exponentialRate?: number } export interface KuFlowWorkerInformation { identity: string taskQueue: string workflowTypes: string[] activityTypes: string[] } export interface KuFlowWorkerInformationNotifierParams { workerInformation: KuFlowWorkerInformation options: KuFlowTemporalConnectionOptions } const HEADER_X_KF_DELAY_WINDOW = 'x-kf-delay-window' export class KuFlowWorkerInformationNotifier { private readonly backoff: Required<KuFlowWorkerInformationNotifierBackoff> private started = false private delayWindowInSeconds = 5 * 60 // 5 min private consecutiveFailures = 0 private scheduleCreateOrUpdateWorkerTimeout?: NodeJS.Timeout public static instance(params: KuFlowWorkerInformationNotifierParams): KuFlowWorkerInformationNotifier { return new KuFlowWorkerInformationNotifier(params.workerInformation, params.options) } private constructor( private readonly workerInformation: KuFlowWorkerInformation, private readonly options: KuFlowTemporalConnectionOptions, ) { const backoff = this.options.kuflow.workerInformationNotifierBackoff this.backoff = { sleep: backoff?.sleep ?? 1, exponentialRate: backoff?.exponentialRate ?? 2.5, } } public async start(): Promise<void> { if (this.started) { return } await this.createOrUpdateWorker() this.scheduleCreateOrUpdateWorker() this.started = true } public async close(): Promise<void> { if (!this.started) { return } this.scheduleCreateOrUpdateWorkerTimeout != null && clearTimeout(this.scheduleCreateOrUpdateWorkerTimeout) this.scheduleCreateOrUpdateWorkerTimeout = undefined this.started = false } private async createOrUpdateWorker(): Promise<void> { const workerRequest: Worker = { identity: this.workerInformation.identity, ip: this.getIPAddress(), hostname: os.hostname(), taskQueue: this.workerInformation.taskQueue, workflowTypes: this.workerInformation.workflowTypes, activityTypes: this.workerInformation.activityTypes, tenantId: this.options.kuflow.tenantId, installationId: this.options.kuflow.installationId, robotIds: this.options.kuflow.robotIds, } const workerOperations = this.options.kuflow.restClient.workerOperations try { let rawResponse: FullOperationResponse | undefined = undefined as FullOperationResponse | undefined const workerResponse = await workerOperations.createWorker(workerRequest, { onResponse: rawResponseInner => { rawResponse = rawResponseInner }, }) Runtime.instance().logger.info( `Registered worker ${workerResponse.taskQueue}/${workerResponse.identity} with id ${workerResponse.id}`, ) this.consecutiveFailures = 0 const delayWindowHeader = rawResponse?.headers.get(HEADER_X_KF_DELAY_WINDOW) if (delayWindowHeader != null) { this.delayWindowInSeconds = parseInt(delayWindowHeader, 10) } } catch (error) { Runtime.instance().logger.error( `There are some problems registering worker ${workerRequest.taskQueue}/${workerRequest.identity}`, { error }, ) this.consecutiveFailures++ } } private scheduleCreateOrUpdateWorker(): void { let delayInSeconds = this.delayWindowInSeconds if (this.consecutiveFailures > 0) { delayInSeconds = Math.round( Math.min(delayInSeconds, this.backoff.sleep * Math.pow(this.backoff.exponentialRate, this.consecutiveFailures)), ) } this.scheduleCreateOrUpdateWorkerTimeout != null && clearTimeout(this.scheduleCreateOrUpdateWorkerTimeout) this.scheduleCreateOrUpdateWorkerTimeout = setTimeout(() => { this.createOrUpdateWorker().then( () => { this.scheduleCreateOrUpdateWorker() }, () => { this.scheduleCreateOrUpdateWorker() }, ) }, delayInSeconds * 1_000) } private getIPAddress(): string { const interfaces = os.networkInterfaces() for (const devName in interfaces) { const iface = interfaces[devName] ?? [] for (let i = 0; i < iface.length; i++) { const alias = iface[i] if (alias.address !== '127.0.0.1' && alias.address !== '0:0:0:0:0:0:0:1' && !alias.internal) { return alias.address } } } return '0.0.0.0' } }