UNPKG

couchbase

Version:

The official Couchbase Node.js Client Library.

157 lines (156 loc) 6.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WaitUntilReadyExecutor = void 0; const diagnosticsexecutor_1 = require("./diagnosticsexecutor"); const diagnosticstypes_1 = require("./diagnosticstypes"); const errors_1 = require("./errors"); const generaltypes_1 = require("./generaltypes"); const utilities_1 = require("./utilities"); const ALL_SERVICE_TYPES = Object.values(generaltypes_1.ServiceType); const BACKOFF_BASE_MS = 50; const BACKOFF_CAP_MS = 500; const BACKOFF_MAX_EXP = 4; /** * @internal */ class WaitUntilReadyExecutor { /** * @internal */ constructor(cluster, bucketName) { this._cluster = cluster; this._bucketName = bucketName; this._diagExec = new diagnosticsexecutor_1.DiagnoticsExecutor(cluster); this._pingExec = new diagnosticsexecutor_1.PingExecutor(cluster); } /** * Checks whether the endpoints for a single service satisfy the desired cluster state. * * @internal */ _meetsDesiredState(endpoints, desiredState) { if (endpoints.length === 0) { return false; } const connected = endpoints.filter((ep) => ep.state === diagnosticstypes_1.EndpointState.Connected).length; if (desiredState === diagnosticstypes_1.ClusterState.Online) { return connected === endpoints.length; } if (desiredState === diagnosticstypes_1.ClusterState.Degraded) { return connected > 0; } return false; } /** * Logs any ping endpoints that reported an error, at warn level. * * @internal */ _logPingErrors(pingResult) { for (const [serviceType, endpoints] of Object.entries(pingResult.services)) { for (const ep of endpoints) { if (ep.state === diagnosticstypes_1.PingState.Error && ep.error) { this._cluster.logger.warn(`waitUntilReady: ping error on service ${serviceType}, remote ${ep.remote}: ${ep.error}`); } } } } /** * Builds the set of services to wait on, either from the user provided list or via discovery ping. * * @internal */ async _resolveWaitSet(serviceTypes, deadline) { let waitSet; if (serviceTypes) { waitSet = [...serviceTypes]; if (this._bucketName === undefined) { const filtered = waitSet.filter((s) => s === generaltypes_1.ServiceType.KeyValue || s === generaltypes_1.ServiceType.Views); if (filtered.length > 0) { this._cluster.logger.warn(`waitUntilReady: ignoring [${filtered.join(', ')}] at cluster scope (no bucket context)`); waitSet = waitSet.filter((s) => s !== generaltypes_1.ServiceType.KeyValue && s !== generaltypes_1.ServiceType.Views); } } } else { const discoveryResult = await this._pingExec.ping({ bucket: this._bucketName, timeout: deadline.left(), }); this._logPingErrors(discoveryResult); waitSet = []; for (const [serviceType, endpoints] of Object.entries(discoveryResult.services)) { if (endpoints.length > 0) { waitSet.push(serviceType); } } } return waitSet; } /** * @internal */ async waitUntilReady(timeout, options) { var _a, _b; if (typeof timeout !== 'number' || !isFinite(timeout) || timeout <= 0) { throw new errors_1.InvalidArgumentError(new Error('timeout must be a positive finite number')); } const desiredState = (_a = options.desiredState) !== null && _a !== void 0 ? _a : diagnosticstypes_1.ClusterState.Online; if (desiredState === diagnosticstypes_1.ClusterState.Offline) { throw new errors_1.InvalidArgumentError(new Error('ClusterState.Offline is not a valid desired state')); } if (options.serviceTypes) { for (const st of options.serviceTypes) { if (!ALL_SERVICE_TYPES.includes(st)) { throw new errors_1.InvalidArgumentError(new Error(`Invalid service type: ${st}`)); } } if (options.serviceTypes.length === 0) { return; } } const deadline = new utilities_1.CompoundTimeout(timeout); const waitSet = await this._resolveWaitSet(options.serviceTypes, deadline); if (waitSet.length === 0) { return; } let iterations = 0; let needPing = [...waitSet]; while (true) { const diagResult = await this._diagExec.diagnostics({}); needPing = []; for (const svc of waitSet) { const endpoints = diagResult.services[svc] || []; if (!this._meetsDesiredState(endpoints, desiredState)) { needPing.push(svc); } } if (needPing.length === 0) { return; } if (deadline.expired()) { throw new errors_1.UnambiguousTimeoutError(new Error('Timed out prior to reaching desired state. ' + 'Services not ready: ' + needPing.join(', '))); } try { const pingResult = await this._pingExec.ping({ serviceTypes: needPing, bucket: this._bucketName, timeout: deadline.left(), }); this._logPingErrors(pingResult); } catch { // One failed ping isn't fatal, try again or eventually time out after deadline is reached. } const backoffMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, Math.min(iterations, BACKOFF_MAX_EXP)), BACKOFF_CAP_MS); const timeLeft = (_b = deadline.left()) !== null && _b !== void 0 ? _b : 0; const sleepMs = Math.min(backoffMs, timeLeft); if (sleepMs > 0) { await new Promise((resolve) => setTimeout(resolve, sleepMs)); } iterations++; } } } exports.WaitUntilReadyExecutor = WaitUntilReadyExecutor;