-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(NODE-5679): introduce timeout abstraction and use for server…
… selection and connection check out (#4078) Co-authored-by: Neal Beeken <neal.beeken@mongodb.com>
- Loading branch information
Showing
8 changed files
with
299 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { clearTimeout, setTimeout } from 'timers'; | ||
|
||
import { MongoInvalidArgumentError } from './error'; | ||
import { noop } from './utils'; | ||
|
||
/** @internal */ | ||
export class TimeoutError extends Error { | ||
override get name(): 'TimeoutError' { | ||
return 'TimeoutError'; | ||
} | ||
|
||
constructor(message: string, options?: { cause?: Error }) { | ||
super(message, options); | ||
} | ||
|
||
static is(error: unknown): error is TimeoutError { | ||
return ( | ||
error != null && typeof error === 'object' && 'name' in error && error.name === 'TimeoutError' | ||
); | ||
} | ||
} | ||
|
||
type Executor = ConstructorParameters<typeof Promise<never>>[0]; | ||
type Reject = Parameters<ConstructorParameters<typeof Promise<never>>[0]>[1]; | ||
/** | ||
* @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 | ||
* */ | ||
export class Timeout extends Promise<never> { | ||
get [Symbol.toStringTag](): 'MongoDBTimeout' { | ||
return 'MongoDBTimeout'; | ||
} | ||
|
||
private timeoutError: TimeoutError; | ||
private id?: NodeJS.Timeout; | ||
|
||
public readonly start: number; | ||
public ended: number | null = null; | ||
public duration: number; | ||
public timedOut = false; | ||
|
||
/** Create a new timeout that expires in `duration` ms */ | ||
private constructor(executor: Executor = () => null, duration: number) { | ||
let reject!: Reject; | ||
|
||
if (duration < 0) { | ||
throw new MongoInvalidArgumentError('Cannot create a Timeout with a negative duration'); | ||
} | ||
|
||
super((_, promiseReject) => { | ||
reject = promiseReject; | ||
|
||
executor(noop, promiseReject); | ||
}); | ||
|
||
// NOTE: Construct timeout error at point of Timeout instantiation to preserve stack traces | ||
this.timeoutError = new TimeoutError(`Expired after ${duration}ms`); | ||
|
||
this.duration = duration; | ||
this.start = Math.trunc(performance.now()); | ||
|
||
if (this.duration > 0) { | ||
this.id = setTimeout(() => { | ||
this.ended = Math.trunc(performance.now()); | ||
this.timedOut = true; | ||
reject(this.timeoutError); | ||
}, this.duration); | ||
// Ensure we do not keep the Node.js event loop running | ||
if (typeof this.id.unref === 'function') { | ||
this.id.unref(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Clears the underlying timeout. This method is idempotent | ||
*/ | ||
clear(): void { | ||
clearTimeout(this.id); | ||
this.id = undefined; | ||
} | ||
|
||
public static expires(durationMS: number): Timeout { | ||
return new Timeout(undefined, durationMS); | ||
} | ||
|
||
static is(timeout: unknown): timeout is Timeout { | ||
return ( | ||
typeof timeout === 'object' && | ||
timeout != null && | ||
Symbol.toStringTag in timeout && | ||
timeout[Symbol.toStringTag] === 'MongoDBTimeout' && | ||
'then' in timeout && | ||
// eslint-disable-next-line github/no-then | ||
typeof timeout.then === 'function' | ||
); | ||
} | ||
} |
Oops, something went wrong.