hamok
Version:
Lightweight Distributed Object Storage on RAFT consensus algorithm
188 lines (187 loc) • 7.3 kB
JavaScript
"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;