You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
10 KiB
291 lines
10 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.LegacyTimeoutContext = exports.CSOTTimeoutContext = exports.TimeoutContext = exports.Timeout = exports.TimeoutError = void 0; |
|
const timers_1 = require("timers"); |
|
const error_1 = require("./error"); |
|
const utils_1 = require("./utils"); |
|
/** @internal */ |
|
class TimeoutError extends Error { |
|
get name() { |
|
return 'TimeoutError'; |
|
} |
|
constructor(message, options) { |
|
super(message, options); |
|
this.duration = options.duration; |
|
} |
|
static is(error) { |
|
return (error != null && typeof error === 'object' && 'name' in error && error.name === 'TimeoutError'); |
|
} |
|
} |
|
exports.TimeoutError = TimeoutError; |
|
/** |
|
* @internal |
|
* This class is an abstraction over timeouts |
|
* The Timeout class can only be in the pending or rejected states. It is guaranteed not to resolve |
|
* if interacted with exclusively through its public API |
|
* */ |
|
class Timeout extends Promise { |
|
get remainingTime() { |
|
if (this.timedOut) |
|
return 0; |
|
if (this.duration === 0) |
|
return Infinity; |
|
return this.start + this.duration - Math.trunc(performance.now()); |
|
} |
|
get timeElapsed() { |
|
return Math.trunc(performance.now()) - this.start; |
|
} |
|
/** Create a new timeout that expires in `duration` ms */ |
|
constructor(executor = () => null, options) { |
|
const duration = options?.duration ?? 0; |
|
const unref = !!options?.unref; |
|
const rejection = options?.rejection; |
|
if (duration < 0) { |
|
throw new error_1.MongoInvalidArgumentError('Cannot create a Timeout with a negative duration'); |
|
} |
|
let reject; |
|
super((_, promiseReject) => { |
|
reject = promiseReject; |
|
executor(utils_1.noop, promiseReject); |
|
}); |
|
this.ended = null; |
|
this.timedOut = false; |
|
this.cleared = false; |
|
this.duration = duration; |
|
this.start = Math.trunc(performance.now()); |
|
if (rejection == null && this.duration > 0) { |
|
this.id = (0, timers_1.setTimeout)(() => { |
|
this.ended = Math.trunc(performance.now()); |
|
this.timedOut = true; |
|
reject(new TimeoutError(`Expired after ${duration}ms`, { duration })); |
|
}, this.duration); |
|
if (typeof this.id.unref === 'function' && unref) { |
|
// Ensure we do not keep the Node.js event loop running |
|
this.id.unref(); |
|
} |
|
} |
|
else if (rejection != null) { |
|
this.ended = Math.trunc(performance.now()); |
|
this.timedOut = true; |
|
reject(rejection); |
|
} |
|
} |
|
/** |
|
* Clears the underlying timeout. This method is idempotent |
|
*/ |
|
clear() { |
|
(0, timers_1.clearTimeout)(this.id); |
|
this.id = undefined; |
|
this.timedOut = false; |
|
this.cleared = true; |
|
} |
|
throwIfExpired() { |
|
if (this.timedOut) |
|
throw new TimeoutError('Timed out', { duration: this.duration }); |
|
} |
|
static expires(duration, unref) { |
|
return new Timeout(undefined, { duration, unref }); |
|
} |
|
static reject(rejection) { |
|
return new Timeout(undefined, { duration: 0, unref: true, rejection }); |
|
} |
|
} |
|
exports.Timeout = Timeout; |
|
function isLegacyTimeoutContextOptions(v) { |
|
return (v != null && |
|
typeof v === 'object' && |
|
'serverSelectionTimeoutMS' in v && |
|
typeof v.serverSelectionTimeoutMS === 'number' && |
|
'waitQueueTimeoutMS' in v && |
|
typeof v.waitQueueTimeoutMS === 'number'); |
|
} |
|
function isCSOTTimeoutContextOptions(v) { |
|
return (v != null && |
|
typeof v === 'object' && |
|
'serverSelectionTimeoutMS' in v && |
|
typeof v.serverSelectionTimeoutMS === 'number' && |
|
'timeoutMS' in v && |
|
typeof v.timeoutMS === 'number'); |
|
} |
|
/** @internal */ |
|
class TimeoutContext { |
|
static create(options) { |
|
if (options.session?.timeoutContext != null) |
|
return options.session?.timeoutContext; |
|
if (isCSOTTimeoutContextOptions(options)) |
|
return new CSOTTimeoutContext(options); |
|
else if (isLegacyTimeoutContextOptions(options)) |
|
return new LegacyTimeoutContext(options); |
|
else |
|
throw new error_1.MongoRuntimeError('Unrecognized options'); |
|
} |
|
} |
|
exports.TimeoutContext = TimeoutContext; |
|
/** @internal */ |
|
class CSOTTimeoutContext extends TimeoutContext { |
|
constructor(options) { |
|
super(); |
|
this.minRoundTripTime = 0; |
|
this.start = Math.trunc(performance.now()); |
|
this.timeoutMS = options.timeoutMS; |
|
this.serverSelectionTimeoutMS = options.serverSelectionTimeoutMS; |
|
this.socketTimeoutMS = options.socketTimeoutMS; |
|
this.clearServerSelectionTimeout = false; |
|
} |
|
get maxTimeMS() { |
|
return this.remainingTimeMS - this.minRoundTripTime; |
|
} |
|
get remainingTimeMS() { |
|
const timePassed = Math.trunc(performance.now()) - this.start; |
|
return this.timeoutMS <= 0 ? Infinity : this.timeoutMS - timePassed; |
|
} |
|
csotEnabled() { |
|
return true; |
|
} |
|
get serverSelectionTimeout() { |
|
// check for undefined |
|
if (typeof this._serverSelectionTimeout !== 'object' || this._serverSelectionTimeout?.cleared) { |
|
const { remainingTimeMS, serverSelectionTimeoutMS } = this; |
|
if (remainingTimeMS <= 0) |
|
return Timeout.reject(new error_1.MongoOperationTimeoutError(`Timed out in server selection after ${this.timeoutMS}ms`)); |
|
const usingServerSelectionTimeoutMS = serverSelectionTimeoutMS !== 0 && |
|
(0, utils_1.csotMin)(remainingTimeMS, serverSelectionTimeoutMS) === serverSelectionTimeoutMS; |
|
if (usingServerSelectionTimeoutMS) { |
|
this._serverSelectionTimeout = Timeout.expires(serverSelectionTimeoutMS); |
|
} |
|
else { |
|
if (remainingTimeMS > 0 && Number.isFinite(remainingTimeMS)) { |
|
this._serverSelectionTimeout = Timeout.expires(remainingTimeMS); |
|
} |
|
else { |
|
this._serverSelectionTimeout = null; |
|
} |
|
} |
|
} |
|
return this._serverSelectionTimeout; |
|
} |
|
get connectionCheckoutTimeout() { |
|
if (typeof this._connectionCheckoutTimeout !== 'object' || |
|
this._connectionCheckoutTimeout?.cleared) { |
|
if (typeof this._serverSelectionTimeout === 'object') { |
|
// null or Timeout |
|
this._connectionCheckoutTimeout = this._serverSelectionTimeout; |
|
} |
|
else { |
|
throw new error_1.MongoRuntimeError('Unreachable. If you are seeing this error, please file a ticket on the NODE driver project on Jira'); |
|
} |
|
} |
|
return this._connectionCheckoutTimeout; |
|
} |
|
get timeoutForSocketWrite() { |
|
const { remainingTimeMS } = this; |
|
if (!Number.isFinite(remainingTimeMS)) |
|
return null; |
|
if (remainingTimeMS > 0) |
|
return Timeout.expires(remainingTimeMS); |
|
return Timeout.reject(new error_1.MongoOperationTimeoutError('Timed out before socket write')); |
|
} |
|
get timeoutForSocketRead() { |
|
const { remainingTimeMS } = this; |
|
if (!Number.isFinite(remainingTimeMS)) |
|
return null; |
|
if (remainingTimeMS > 0) |
|
return Timeout.expires(remainingTimeMS); |
|
return Timeout.reject(new error_1.MongoOperationTimeoutError('Timed out before socket read')); |
|
} |
|
refresh() { |
|
this.start = Math.trunc(performance.now()); |
|
this.minRoundTripTime = 0; |
|
this._serverSelectionTimeout?.clear(); |
|
this._connectionCheckoutTimeout?.clear(); |
|
} |
|
clear() { |
|
this._serverSelectionTimeout?.clear(); |
|
this._connectionCheckoutTimeout?.clear(); |
|
} |
|
/** |
|
* @internal |
|
* Throws a MongoOperationTimeoutError if the context has expired. |
|
* If the context has not expired, returns the `remainingTimeMS` |
|
**/ |
|
getRemainingTimeMSOrThrow(message) { |
|
const { remainingTimeMS } = this; |
|
if (remainingTimeMS <= 0) |
|
throw new error_1.MongoOperationTimeoutError(message ?? `Expired after ${this.timeoutMS}ms`); |
|
return remainingTimeMS; |
|
} |
|
/** |
|
* @internal |
|
* This method is intended to be used in situations where concurrent operation are on the same deadline, but cannot share a single `TimeoutContext` instance. |
|
* Returns a new instance of `CSOTTimeoutContext` constructed with identical options, but setting the `start` property to `this.start`. |
|
*/ |
|
clone() { |
|
const timeoutContext = new CSOTTimeoutContext({ |
|
timeoutMS: this.timeoutMS, |
|
serverSelectionTimeoutMS: this.serverSelectionTimeoutMS |
|
}); |
|
timeoutContext.start = this.start; |
|
return timeoutContext; |
|
} |
|
refreshed() { |
|
return new CSOTTimeoutContext(this); |
|
} |
|
addMaxTimeMSToCommand(command, options) { |
|
if (options.omitMaxTimeMS) |
|
return; |
|
const maxTimeMS = this.remainingTimeMS - this.minRoundTripTime; |
|
if (maxTimeMS > 0 && Number.isFinite(maxTimeMS)) |
|
command.maxTimeMS = maxTimeMS; |
|
} |
|
getSocketTimeoutMS() { |
|
return 0; |
|
} |
|
} |
|
exports.CSOTTimeoutContext = CSOTTimeoutContext; |
|
/** @internal */ |
|
class LegacyTimeoutContext extends TimeoutContext { |
|
constructor(options) { |
|
super(); |
|
this.options = options; |
|
this.clearServerSelectionTimeout = true; |
|
} |
|
csotEnabled() { |
|
return false; |
|
} |
|
get serverSelectionTimeout() { |
|
if (this.options.serverSelectionTimeoutMS != null && this.options.serverSelectionTimeoutMS > 0) |
|
return Timeout.expires(this.options.serverSelectionTimeoutMS); |
|
return null; |
|
} |
|
get connectionCheckoutTimeout() { |
|
if (this.options.waitQueueTimeoutMS != null && this.options.waitQueueTimeoutMS > 0) |
|
return Timeout.expires(this.options.waitQueueTimeoutMS); |
|
return null; |
|
} |
|
get timeoutForSocketWrite() { |
|
return null; |
|
} |
|
get timeoutForSocketRead() { |
|
return null; |
|
} |
|
refresh() { |
|
return; |
|
} |
|
clear() { |
|
return; |
|
} |
|
get maxTimeMS() { |
|
return null; |
|
} |
|
refreshed() { |
|
return new LegacyTimeoutContext(this.options); |
|
} |
|
addMaxTimeMSToCommand(_command, _options) { |
|
// No max timeMS is added to commands in legacy timeout mode. |
|
} |
|
getSocketTimeoutMS() { |
|
return this.options.socketTimeoutMS; |
|
} |
|
} |
|
exports.LegacyTimeoutContext = LegacyTimeoutContext; |
|
//# sourceMappingURL=timeout.js.map
|