UNPKG

hamok

Version:

Lightweight Distributed Object Storage on RAFT consensus algorithm

188 lines (187 loc) 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PendingRequest = void 0; const logger_1 = require("../common/logger"); const logger = (0, logger_1.createLogger)('PendingRequest'); class PendingRequest { config; responses = new Map(); _postponeTimeout = false; _state = 'pending'; _receivedResponses = 0; _timer; _promise; _resolve; _reject; // private _promise?: CompletablePromise<Message[]>; constructor(config) { this.config = config; if (this.config.timeoutInMs) { if (this.config.timeoutInMs < 1) { throw new Error('Timeout for a pending promise if given must be greater than 0'); } const process = () => { if (this._timer === undefined) { return; } this._timer = undefined; if (!this._promise) { return; } if (this._postponeTimeout) { this._postponeTimeout = false; return (this._timer = setTimeout(process, this.config.timeoutInMs)); } if (this._reject) { this._reject(`Timeout. requestId: ${this.id}, number of received response: ${this._receivedResponses}`); } else { logger.warn(`No reject function is defined for pending promise ${this} but the timeout elpased.`); } }; this._timer = setTimeout(process, this.config.timeoutInMs); logger.trace('Pending Request %s is created with timeout %d', this, this.config.timeoutInMs); } this._promise = new Promise((resolve, reject) => { this._resolve = () => { if (this._timer) { clearTimeout(this._timer); this._timer = undefined; } const response = Array.from(this.responses.values()); logger.trace(`Pending request is resolved by responses ${response}`); /* eslint-disable @typescript-eslint/no-non-null-assertion */ resolve(response); this._resolve = undefined; this._reject = undefined; this._state = 'resolved'; }; this._reject = (reason) => { if (this._timer) { clearTimeout(this._timer); this._timer = undefined; } reject(reason); this._resolve = undefined; this._reject = undefined; this._state = 'rejected'; }; }); } get id() { return this.config.requestId; } get completed() { return this._state !== 'pending'; } get state() { return this._state; } get promise() { return this._promise; } // public then<TResult1 = HamokMessage[], TResult2 = never>(onfulfilled?: ((value: HamokMessage[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: string) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2> { // return this._promise.then(onfulfilled, onrejected); // } // public catch<TResult = never>(onrejected?: ((reason: string) => TResult | PromiseLike<TResult>) | null): Promise<HamokMessage[] | TResult> { // return this._promise.catch(onrejected); // } // public finally(onfinally?: (() => void) | null): Promise<HamokMessage[]> { // return this._promise.finally(onfinally); // } // private _resolve(): void { // if (this._timer) { // clearTimeout(this._timer); // this._timer = undefined; // } // if (!this._promise) { // return logger.warn(`Attempted to resolve a not pending request (${this})`); // } // const response = Array.from(this._responses.values()); // logger.trace(`Pending request is resolved by responses ${response}`); // /* eslint-disable @typescript-eslint/no-non-null-assertion */ // this._promise!.resolve(response); // } accept(message) { if (message.sourceId === undefined || message.requestId === undefined) { logger.warn('No source or request id is assigned for message:', message); return; } const prevResponse = this.responses.get(message.sourceId); if (prevResponse) { logger.warn(`Remote endpoint ${message.sourceId} overrided its previous response for request ${this.id}. removed response`, message); } this.responses.set(message.sourceId, message); this.refresh(); } /** * Explicitly reject the pending request if it is in the pending state * @param reason * @returns */ reject(reason) { if (this.completed || !this._reject) return logger.warn(`Attempted to reject a not pending request (${this})`); this._reject(reason); } /** * Explicitly resolve the pending request if it is in the pending state * @returns */ resolve() { if (this.completed || !this._resolve) return logger.warn(`Attempted to resolve a not pending request (${this})`); if (!this.isReady) { logger.warn(`Resolving a pending request ${this} before it is ready`); } this._resolve(); } /** * Check if the pending request can be resolved or not * This is automatically called after any message accepted, but if the endpoints changed or * some other external event happened it can be called explicitly. * @returns */ refresh() { if (this.completed || !this._resolve) return; if (!this.isReady) return; this._resolve(); } get isReady() { let noMoreNeededResponse = true; if (this.config.neededResponses) { ++this._receivedResponses; noMoreNeededResponse = this.config.neededResponses <= this._receivedResponses; } let noMorePendingPeers = true; if (this.config.remotePeers) { let pendingPeerIds = this.config.remotePeers.size; for (const resolvedPeerId of this.responses.keys()) { if (this.config.remotePeers.has(resolvedPeerId)) { --pendingPeerIds; } else { logger.warn(`Remote peer ${resolvedPeerId} is not in the list of remote peers for request ${this}`); } } noMorePendingPeers = pendingPeerIds < 1; } return noMoreNeededResponse && noMorePendingPeers; } postponeTimeout() { this._postponeTimeout = true; logger.debug(`Pending Request ${this} is postponed`); } get [Symbol.toStringTag]() { return [ `PendingRequest (${this.id})`, `neededResponses: ${this.config.neededResponses}`, `remotePeers: ${Array.from(this.config.remotePeers ?? []).join(', ')}`, `timeoutInMs: ${this.config.timeoutInMs}`, `state: ${this._state}`, ].join(', '); } } exports.PendingRequest = PendingRequest;