Skip to content

Commit f822856

Browse files
committed
fix(MongoInstance): add a timeout for the "launch" promise
re #710
1 parent 1c60f61 commit f822856

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

packages/mongodb-memory-server-core/src/util/MongoInstance.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { lt } from 'semver';
1414
import { EventEmitter } from 'events';
1515
import { MongoClient, MongoClientOptions, MongoNetworkError } from 'mongodb';
1616
import {
17+
GenericMMSError,
1718
KeyFileMissingError,
1819
StartBinaryFailedError,
1920
StdoutInstanceError,
@@ -165,6 +166,12 @@ export interface MongoMemoryInstanceOpts extends MongoMemoryInstanceOptsBase {
165166
* @default undefined
166167
*/
167168
keyfileLocation?: string;
169+
/**
170+
* Define a custom timeout for when out of some reason the binary cannot get started correctly
171+
* Time in MS
172+
* @default 10000 10 seconds
173+
*/
174+
launchTimeout?: number;
168175
}
169176

170177
export enum MongoInstanceEvents {
@@ -336,12 +343,30 @@ export class MongoInstance extends EventEmitter implements ManagerBase {
336343
this.isInstanceReady = false;
337344
this.isReplSet = false;
338345

339-
const launch: Promise<void> = new Promise((res, rej) => {
346+
let timeout: NodeJS.Timeout;
347+
348+
const launch: Promise<void> = new Promise<void>((res, rej) => {
340349
this.once(MongoInstanceEvents.instanceReady, res);
341350
this.once(MongoInstanceEvents.instanceError, rej);
342351
this.once(MongoInstanceEvents.instanceClosed, function launchInstanceClosed() {
343352
rej(new Error('Instance Exited before being ready and without throwing an error!'));
344353
});
354+
355+
// extra conditions just to be sure that the custom defined timeout is valid
356+
const timeoutTime =
357+
!!this.instanceOpts.launchTimeout && this.instanceOpts.launchTimeout >= 1000
358+
? this.instanceOpts.launchTimeout
359+
: 1000 * 10; // default 10 seconds
360+
361+
timeout = setTimeout(() => {
362+
const err = new GenericMMSError(`Instance failed to start within ${timeoutTime}ms`);
363+
this.emit(MongoInstanceEvents.instanceError, err);
364+
365+
rej(err);
366+
}, timeoutTime);
367+
}).finally(() => {
368+
// always clear the timeout after the promise somehow resolves
369+
clearTimeout(timeout);
345370
});
346371

347372
const mongoBin = await MongoBinary.getPath(this.binaryOpts);

packages/mongodb-memory-server-core/src/util/__tests__/MongoInstance.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import * as dbUtil from '../utils';
44
import MongodbInstance, { MongoInstanceEvents } from '../MongoInstance';
55
import resolveConfig, { ResolveConfigVariables } from '../resolveConfig';
66
import getPort from 'get-port';
7-
import { StartBinaryFailedError, StdoutInstanceError, UnexpectedCloseError } from '../errors';
7+
import {
8+
GenericMMSError,
9+
StartBinaryFailedError,
10+
StdoutInstanceError,
11+
UnexpectedCloseError,
12+
} from '../errors';
813
import { assertIsError } from '../../__tests__/testUtils/test_utils';
914

1015
jest.setTimeout(100000); // 10s
@@ -17,6 +22,7 @@ beforeEach(() => {
1722

1823
afterEach(() => {
1924
tmpDir.removeCallback();
25+
jest.restoreAllMocks();
2026
});
2127

2228
describe('MongodbInstance', () => {
@@ -597,5 +603,42 @@ describe('MongodbInstance', () => {
597603
expect(event.message).toMatchSnapshot();
598604
});
599605
});
606+
607+
it('"start" should emit a "instanceError" when timeout is reached and throw a error', async () => {
608+
mongod.instanceOpts['launchTimeout'] = 1000;
609+
610+
jest.spyOn(mongod, '_launchMongod').mockImplementation(
611+
// @ts-expect-error The following is not meant to work, but in this test we dont care about that result, only that it never fires any events
612+
() => {
613+
return { pid: 0 }; // required for a direct check afterwards
614+
}
615+
);
616+
jest.spyOn(mongod, '_launchKiller').mockImplementation(
617+
// @ts-expect-error The following is not meant to work, but in this test we dont care about that result, only that it never fires any events
618+
() => undefined
619+
);
620+
jest.spyOn(mongod, 'stop').mockImplementation(
621+
// @ts-expect-error The following is not meant to work, but in this test we dont care about that result, only that it never fires any events
622+
() => undefined
623+
);
624+
625+
try {
626+
await mongod.start();
627+
fail('Expected "start" to throw');
628+
} catch (err) {
629+
// this error could be thrown through "once => instanceError" or from the timeout directly, but it does not matter where it gets thrown from
630+
expect(err).toBeInstanceOf(GenericMMSError);
631+
assertIsError(err);
632+
expect(err.message).toMatchSnapshot();
633+
634+
expect(events.size).toEqual(1);
635+
636+
const event = events.get(MongoInstanceEvents.instanceError)?.[0];
637+
expect(event).toBeInstanceOf(GenericMMSError);
638+
assertIsError(event);
639+
expect(event.message).toStrictEqual(err.message);
640+
expect(err).toBe(event); // reference compare, because these 2 values should be the same
641+
}
642+
});
600643
});
601644
});

packages/mongodb-memory-server-core/src/util/__tests__/__snapshots__/MongoInstance.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ exports[`MongodbInstance test events "closeHandler" should emit "instanceError"
1717

1818
exports[`MongodbInstance test events "closeHandler" should emit "instanceError" with non-0 or non-12 code 2`] = `"Instance closed unexpectedly with code \\"null\\" and signal \\"SIG\\""`;
1919

20+
exports[`MongodbInstance test events "start" should emit a "instanceError" when timeout is reached and throw a error 1`] = `"Instance failed to start within 1000ms"`;
21+
2022
exports[`MongodbInstance test events checkErrorInLine() should emit "instanceError" when shared libraries fail to load 1`] = `"Instance failed to start because a library is missing or cannot be opened: \\"libcrypto.so.1.1\\""`;
2123

2224
exports[`MongodbInstance test events stdoutHandler() should emit "instanceError" when "excepetion in initAndListen" is thrown DBException in initAndListen 1`] = `

0 commit comments

Comments
 (0)