From 94ca4d917c6eb52e92bfaf708c7b5d4bb90ef4db Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 11 Aug 2021 12:56:45 +0200 Subject: [PATCH] feat(shell-api): add maxTimeMS config options MONGOSH-537 (#1068) --- packages/cli-repl/src/cli-repl.spec.ts | 1 + packages/shell-api/src/collection.ts | 99 +++++++++---------- packages/shell-api/src/database.ts | 43 ++++---- packages/shell-api/src/helpers.ts | 2 +- packages/shell-api/src/integration.spec.ts | 36 +++++++ packages/shell-api/src/interruptor.spec.ts | 10 +- packages/shell-api/src/shell-api.spec.ts | 2 +- .../src/shell-internal-state.spec.ts | 4 +- packages/types/src/index.spec.ts | 5 + packages/types/src/index.ts | 6 ++ 10 files changed, 132 insertions(+), 76 deletions(-) diff --git a/packages/cli-repl/src/cli-repl.spec.ts b/packages/cli-repl/src/cli-repl.spec.ts index feb277718..0dd29cbab 100644 --- a/packages/cli-repl/src/cli-repl.spec.ts +++ b/packages/cli-repl/src/cli-repl.spec.ts @@ -200,6 +200,7 @@ describe('CliRepl', () => { it('returns the list of available config options when asked to', () => { expect(cliRepl.listConfigOptions()).to.deep.equal([ 'displayBatchSize', + 'maxTimeMS', 'enableTelemetry', 'snippetIndexSourceURLs', 'snippetRegistryURL', diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index 46d722a49..8e54b06be 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -174,7 +174,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, pipeline, - { ...this._database._baseOptions, ...aggOptions }, + { ...await this._database._baseOptions(), ...aggOptions }, dbOptions ); const cursor = new AggregationCursor(this._mongo, providerCursor); @@ -215,7 +215,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, operations, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); return new BulkWriteResult( @@ -255,7 +255,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); } @@ -277,7 +277,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); } @@ -303,7 +303,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, filter, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); if (options.explain) { return markAsExplainOutput(result); @@ -337,7 +337,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, filter, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); if (options.explain) { return markAsExplainOutput(result); @@ -374,7 +374,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, field, query, - { ...this._database._baseOptions, ...options }), + { ...await this._database._baseOptions(), ...options }), options); } @@ -391,7 +391,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([1]) async estimatedDocumentCount(options: EstimatedDocumentCountOptions = {}): Promise { this._emitCollectionApiCall('estimatedDocumentCount', { options }); - return this._mongo._serviceProvider.estimatedDocumentCount(this._database._name, this._name, { ...this._database._baseOptions, ...options }); + return this._mongo._serviceProvider.estimatedDocumentCount(this._database._name, this._name, { ...await this._database._baseOptions(), ...options }); } /** @@ -405,7 +405,6 @@ export default class Collection extends ShellApiWithMongoClass { * * @returns {Cursor} The promise of the cursor. */ - // eslint-disable-next-line @typescript-eslint/require-await @returnType('Cursor') @apiVersions([1]) @returnsPromise @@ -421,7 +420,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ) ); @@ -482,7 +481,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ) ).limit(1).tryNext(); } @@ -501,7 +500,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, newName, - { ...this._database._baseOptions, dropTarget: !!dropTarget } + { ...await this._database._baseOptions(), dropTarget: !!dropTarget } ); return { @@ -541,7 +540,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, filter, - { ...this._database._baseOptions, ...options }, + { ...await this._database._baseOptions(), ...options }, ); if (options.explain) { @@ -571,7 +570,7 @@ export default class Collection extends ShellApiWithMongoClass { async findOneAndReplace(filter: Document, replacement: Document, options: FindAndModifyShellOptions = {}): Promise { assertArgsDefinedType([filter], [true], 'Collection.findOneAndReplace'); const findOneAndReplaceOptions = processFindAndModifyOptions({ - ...this._database._baseOptions, + ...await this._database._baseOptions(), ...options }); @@ -610,7 +609,7 @@ export default class Collection extends ShellApiWithMongoClass { async findOneAndUpdate(filter: Document, update: Document | Document[], options: FindAndModifyShellOptions = {}): Promise { assertArgsDefinedType([filter], [true], 'Collection.findOneAndUpdate'); const findOneAndUpdateOptions = processFindAndModifyOptions({ - ...this._database._baseOptions, + ...await this._database._baseOptions(), ...options }); @@ -662,7 +661,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, docsToInsert, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); return new InsertManyResult( @@ -696,7 +695,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, docsToInsert, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); return new InsertManyResult( @@ -729,7 +728,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, { ...doc }, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); return new InsertOneResult( @@ -781,7 +780,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...this._database._baseOptions, ...removeOptions } + { ...await this._database._baseOptions(), ...removeOptions } ); if (removeOptions.explain) { return markAsExplainOutput(result); @@ -826,7 +825,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, filter, replacement, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); return new UpdateResult( !!result.acknowledged, @@ -856,7 +855,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, filter, update, - { ...this._database._baseOptions, ...options }, + { ...await this._database._baseOptions(), ...options }, ); } else { result = await this._mongo._serviceProvider.updateOne( @@ -864,7 +863,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, filter, update, - { ...this._database._baseOptions, ...options }, + { ...await this._database._baseOptions(), ...options }, ); } if (options.explain) { @@ -903,7 +902,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, filter, update, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); if (options.explain) { return markAsExplainOutput(result); @@ -946,7 +945,7 @@ export default class Collection extends ShellApiWithMongoClass { this._name, filter, update, - { ...this._database._baseOptions, ...options } + { ...await this._database._baseOptions(), ...options } ); if (options.explain) { return markAsExplainOutput(result); @@ -977,7 +976,7 @@ export default class Collection extends ShellApiWithMongoClass { convertToCapped: this._name, size }, - this._database._baseOptions + await this._database._baseOptions() ); } @@ -1013,7 +1012,7 @@ export default class Collection extends ShellApiWithMongoClass { this._emitCollectionApiCall('createIndexes', { specs }); return await this._mongo._serviceProvider.createIndexes( - this._database._name, this._name, specs, { ...this._database._baseOptions, ...options }); + this._database._name, this._name, specs, { ...await this._database._baseOptions(), ...options }); } /** @@ -1044,7 +1043,7 @@ export default class Collection extends ShellApiWithMongoClass { const spec = { key: keys, ...options }; // keep options for java const names = await this._mongo._serviceProvider.createIndexes( - this._database._name, this._name, [spec], { ...this._database._baseOptions, ...options }); + this._database._name, this._name, [spec], { ...await this._database._baseOptions(), ...options }); if (!Array.isArray(names) || names.length !== 1) { throw new MongoshInternalError( `Expected createIndexes() to return array of length 1, saw ${names}`); @@ -1079,7 +1078,7 @@ export default class Collection extends ShellApiWithMongoClass { this._emitCollectionApiCall('ensureIndex', { keys, options }); const spec = { key: keys, ...options }; - return await this._mongo._serviceProvider.createIndexes(this._database._name, this._name, [spec], { ...this._database._baseOptions, ...options }); + return await this._mongo._serviceProvider.createIndexes(this._database._name, this._name, [spec], { ...await this._database._baseOptions(), ...options }); } /** @@ -1093,7 +1092,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([1]) async getIndexes(): Promise { this._emitCollectionApiCall('getIndexes'); - return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, this._database._baseOptions); + return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, await this._database._baseOptions()); } /** @@ -1107,7 +1106,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([1]) async getIndexSpecs(): Promise { this._emitCollectionApiCall('getIndexSpecs'); - return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, this._database._baseOptions); + return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, await this._database._baseOptions()); } /** @@ -1120,7 +1119,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([1]) async getIndices(): Promise { this._emitCollectionApiCall('getIndices'); - return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, this._database._baseOptions); + return await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, await this._database._baseOptions()); } /** @@ -1133,7 +1132,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([1]) async getIndexKeys(): Promise { this._emitCollectionApiCall('getIndexKeys'); - const indexes = await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, this._database._baseOptions); + const indexes = await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, await this._database._baseOptions()); return indexes.map(i => i.key); } @@ -1155,7 +1154,7 @@ export default class Collection extends ShellApiWithMongoClass { dropIndexes: this._name, index: indexes, }, - this._database._baseOptions); + await this._database._baseOptions()); } catch (error) { // If indexes is an array and we're failing because of that, we fall back to // trying to drop all the indexes individually because that's what's supported @@ -1230,7 +1229,7 @@ export default class Collection extends ShellApiWithMongoClass { ); } - const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, this._database._baseOptions); + const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, await this._database._baseOptions()); return stats.totalIndexSize; } @@ -1246,7 +1245,7 @@ export default class Collection extends ShellApiWithMongoClass { this._emitCollectionApiCall('reIndex'); return await this._mongo._serviceProvider.runCommandWithCheck(this._database._name, { reIndex: this._name - }, this._database._baseOptions); + }, await this._database._baseOptions()); } /** @@ -1280,7 +1279,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([]) async dataSize(): Promise { this._emitCollectionApiCall('dataSize'); - const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, this._database._baseOptions); + const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, await this._database._baseOptions()); return stats.size; } @@ -1293,7 +1292,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([]) async storageSize(): Promise { this._emitCollectionApiCall('storageSize'); - const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, this._database._baseOptions); + const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, await this._database._baseOptions()); return stats.storageSize; } @@ -1306,7 +1305,7 @@ export default class Collection extends ShellApiWithMongoClass { @apiVersions([]) async totalSize(): Promise { this._emitCollectionApiCall('totalSize'); - const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, this._database._baseOptions); + const stats = await this._mongo._serviceProvider.stats(this._database._name, this._name, await this._database._baseOptions()); return (stats.storageSize || 0) + (stats.totalIndexSize || 0); } @@ -1324,7 +1323,7 @@ export default class Collection extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.dropCollection( this._database._name, this._name, - this._database._baseOptions + await this._database._baseOptions() ); } catch (error) { if (error.codeName === 'NamespaceNotFound') { @@ -1356,7 +1355,7 @@ export default class Collection extends ShellApiWithMongoClass { { name: this._name }, - this._database._baseOptions + await this._database._baseOptions() ); return collectionInfos[0] || null; @@ -1401,7 +1400,7 @@ export default class Collection extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.runCommandWithCheck( this._database._name, cmd, - this._database._baseOptions + await this._database._baseOptions() ); } @@ -1446,7 +1445,7 @@ export default class Collection extends ShellApiWithMongoClass { { collStats: this._name, scale: options.scale }, - this._database._baseOptions + await this._database._baseOptions() ); if (!result) { throw new MongoshRuntimeError( @@ -1456,7 +1455,7 @@ export default class Collection extends ShellApiWithMongoClass { } let filterIndexName = options.indexDetailsName; if (!filterIndexName && options.indexDetailsKey) { - const indexes = await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, this._database._baseOptions); + const indexes = await this._mongo._serviceProvider.getIndexes(this._database._name, this._name, await this._database._baseOptions()); indexes.forEach((spec) => { if (JSON.stringify(spec.key) === JSON.stringify(options.indexDetailsKey)) { filterIndexName = spec.name; @@ -1503,7 +1502,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, pipeline, - this._database._baseOptions + await this._database._baseOptions() ); return await providerCursor.toArray(); } @@ -1517,7 +1516,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, true, - this._database._baseOptions + await this._database._baseOptions() ); return new Bulk(this, innerBulk, true); } @@ -1531,7 +1530,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, false, - this._database._baseOptions + await this._database._baseOptions() ); return new Bulk(this, innerBulk); } @@ -1574,7 +1573,7 @@ export default class Collection extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.runCommandWithCheck( this._database._name, cmd, - this._database._baseOptions + await this._database._baseOptions() ); } @@ -1591,7 +1590,7 @@ export default class Collection extends ShellApiWithMongoClass { validate: this._name, ...options }, - this._database._baseOptions + await this._database._baseOptions() ); } @@ -1605,7 +1604,7 @@ export default class Collection extends ShellApiWithMongoClass { { getShardVersion: `${this._database._name}.${this._name}` }, - this._database._baseOptions + await this._database._baseOptions() ); } @@ -1721,7 +1720,7 @@ export default class Collection extends ShellApiWithMongoClass { this._emitCollectionApiCall('watch', { pipeline, options }); const cursor = new ChangeStreamCursor( this._mongo._serviceProvider.watch(pipeline, { - ...this._database._baseOptions, + ...await this._database._baseOptions(), ...options }, {}, this._database._name, this._name), this._name, diff --git a/packages/shell-api/src/database.ts b/packages/shell-api/src/database.ts index 10d800cff..388e08a62 100644 --- a/packages/shell-api/src/database.ts +++ b/packages/shell-api/src/database.ts @@ -58,7 +58,6 @@ export default class Database extends ShellApiWithMongoClass { _mongo: Mongo; _name: string; _collections: Record; - _baseOptions: CommandOperationOptions; _session: Session | undefined; _cachedCollectionNames: string[] = []; @@ -68,11 +67,7 @@ export default class Database extends ShellApiWithMongoClass { this._name = name; const collections: Record = {}; this._collections = collections; - this._baseOptions = {}; - if (session !== undefined) { - this._session = session; - this._baseOptions.session = session._session; - } + this._session = session; const proxy = new Proxy(this, { get: (target, prop): any => { if (prop in target) { @@ -97,6 +92,18 @@ export default class Database extends ShellApiWithMongoClass { return proxy; } + async _baseOptions(): Promise { + const options: CommandOperationOptions = {}; + if (this._session) { + options.session = this._session._session; + } + const maxTimeMS = await this._internalState.shellApi.config.get('maxTimeMS'); + if (typeof maxTimeMS === 'number') { + options.maxTimeMS = maxTimeMS; + } + return options; + } + /** * Internal method to determine what is printed for this class. */ @@ -126,7 +133,7 @@ export default class Database extends ShellApiWithMongoClass { return this._mongo._serviceProvider.runCommandWithCheck( this._name, cmd, - { ...this._mongo._getExplicitlyRequestedReadPref(), ...this._baseOptions, ...options } + { ...this._mongo._getExplicitlyRequestedReadPref(), ...await this._baseOptions(), ...options } ); } @@ -134,7 +141,7 @@ export default class Database extends ShellApiWithMongoClass { return this._mongo._serviceProvider.runCommandWithCheck( ADMIN_DB, cmd, - { ...this._mongo._getExplicitlyRequestedReadPref(), ...this._baseOptions, ...options } + { ...this._mongo._getExplicitlyRequestedReadPref(), ...await this._baseOptions(), ...options } ); } @@ -142,7 +149,7 @@ export default class Database extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.listCollections( this._name, filter, - { ...this._mongo._getExplicitlyRequestedReadPref(), ...this._baseOptions, ...options } + { ...this._mongo._getExplicitlyRequestedReadPref(), ...await this._baseOptions(), ...options } ) || []; } @@ -202,7 +209,7 @@ export default class Database extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.runCommand( this._name, cmd, - this._baseOptions + await this._baseOptions() ); } catch (e) { return e; @@ -317,7 +324,7 @@ export default class Database extends ShellApiWithMongoClass { const providerCursor = this._mongo._serviceProvider.aggregateDb( this._name, pipeline, - { ...this._baseOptions, ...aggOptions }, + { ...await this._baseOptions(), ...aggOptions }, dbOptions ); const cursor = new AggregationCursor(this._mongo, providerCursor); @@ -362,7 +369,7 @@ export default class Database extends ShellApiWithMongoClass { async dropDatabase(writeConcern?: WriteConcern): Promise { return await this._mongo._serviceProvider.dropDatabase( this._name, - { ...this._baseOptions, writeConcern } + { ...await this._baseOptions(), writeConcern } ); } @@ -591,7 +598,7 @@ export default class Database extends ShellApiWithMongoClass { return await this._mongo._serviceProvider.createCollection( this._name, name, - { ...this._baseOptions, ...options } + { ...await this._baseOptions(), ...options } ); } @@ -601,7 +608,7 @@ export default class Database extends ShellApiWithMongoClass { assertArgsDefinedType([name, source, pipeline], ['string', 'string', true], 'Database.createView'); this._emitDatabaseApiCall('createView', { name, source, pipeline, options }); const ccOpts = { - ...this._baseOptions, + ...await this._baseOptions(), viewOn: source, pipeline: pipeline } as Document; @@ -1032,7 +1039,7 @@ export default class Database extends ShellApiWithMongoClass { setFreeMonitoring: 1, action: 'enable' }, - this._baseOptions + await this._baseOptions() ); let result: any; let error: any; @@ -1040,7 +1047,7 @@ export default class Database extends ShellApiWithMongoClass { result = await this._mongo._serviceProvider.runCommand( ADMIN_DB, { getFreeMonitoringStatus: 1 }, - this._baseOptions + await this._baseOptions() ); } catch (err) { error = err; @@ -1060,7 +1067,7 @@ export default class Database extends ShellApiWithMongoClass { getParameter: 1, cloudFreeMonitoringEndpointURL: 1 }, - this._baseOptions + await this._baseOptions() ); return `Unable to get immediate response from the Cloud Monitoring service. Please check your firewall settings to ensure that mongod can communicate with '${urlResult.cloudFreeMonitoringEndpointURL || ''}'`; } @@ -1419,7 +1426,7 @@ export default class Database extends ShellApiWithMongoClass { this._emitDatabaseApiCall('watch', { pipeline, options }); const cursor = new ChangeStreamCursor( this._mongo._serviceProvider.watch(pipeline, { - ...this._baseOptions, + ...await this._baseOptions(), ...options }, {}, this._name), this._name, diff --git a/packages/shell-api/src/helpers.ts b/packages/shell-api/src/helpers.ts index 9a36f1ccc..de11f3577 100644 --- a/packages/shell-api/src/helpers.ts +++ b/packages/shell-api/src/helpers.ts @@ -636,7 +636,7 @@ export async function setHideIndex(coll: Collection, index: string | Document, h collMod: coll._name, index: cmd }, - coll._database._baseOptions + await coll._database._baseOptions() ); } diff --git a/packages/shell-api/src/integration.spec.ts b/packages/shell-api/src/integration.spec.ts index b563aa6ba..d4a5ca8de 100644 --- a/packages/shell-api/src/integration.spec.ts +++ b/packages/shell-api/src/integration.spec.ts @@ -2204,6 +2204,42 @@ describe('Shell API (integration)', function() { }); }); + describe('maxTimeMS support', () => { + skipIfServerVersion(testServer, '< 4.2'); + + beforeEach(async() => { + await collection.insertMany([...Array(10).keys()].map(i => ({ i }))); + const cfg = new ShellUserConfig(); + internalState.setEvaluationListener({ + getConfig(key: string) { return cfg[key]; }, + setConfig(key: string, value: any) { cfg[key] = value; return 'success'; }, + listConfigOptions() { return Object.keys(cfg); } + }); + }); + + it('config changes affect maxTimeMS', async() => { + await shellApi.config.set('maxTimeMS', 100); + try { + // eslint-disable-next-line no-constant-condition + await (await collection.find({ $where: function() { while (true); } })).next(); + expect.fail('missed exception'); + } catch (err) { + expect(err.codeName).to.equal('MaxTimeMSExpired'); + } + }); + + it('maxTimeMS can be passed explicitly', async() => { + await shellApi.config.set('maxTimeMS', null); + try { + // eslint-disable-next-line no-constant-condition + await (await collection.find({ $where: function() { while (true); } }, {}, { maxTimeMS: 100 })).next(); + expect.fail('missed exception'); + } catch (err) { + expect(err.codeName).to.equal('MaxTimeMSExpired'); + } + }); + }); + describe('cursor map/forEach', () => { beforeEach(async() => { await collection.insertMany([...Array(10).keys()].map(i => ({ i }))); diff --git a/packages/shell-api/src/interruptor.spec.ts b/packages/shell-api/src/interruptor.spec.ts index 042c55207..7a55f595b 100644 --- a/packages/shell-api/src/interruptor.spec.ts +++ b/packages/shell-api/src/interruptor.spec.ts @@ -80,13 +80,13 @@ describe('interruptor', () => { it('causes an interrupt error to be thrown on exit', async() => { let resolveCall: (result: any) => void; - serviceProvider.runCommandWithCheck.callsFake(() => { - return new Promise(resolve => { - resolveCall = resolve; - }); - }); + serviceProvider.runCommandWithCheck.resolves(new Promise(resolve => { + resolveCall = resolve; + })); const runCommand = database.runCommand({ some: 1 }); + await new Promise(setImmediate); + await new Promise(setImmediate); // ticks due to db._baseOptions() being async internalState.interrupted.set(); resolveCall({ ok: 1 }); diff --git a/packages/shell-api/src/shell-api.spec.ts b/packages/shell-api/src/shell-api.spec.ts index 9fed271b3..0e100963c 100644 --- a/packages/shell-api/src/shell-api.spec.ts +++ b/packages/shell-api/src/shell-api.spec.ts @@ -731,7 +731,7 @@ describe('ShellApi', () => { it('will work with defaults', async() => { expect(await config.get('displayBatchSize')).to.equal(20); expect((await toShellResult(config)).printable).to.deep.equal( - new Map([['displayBatchSize', 20], ['enableTelemetry', false]] as any)); + new Map([['displayBatchSize', 20], ['maxTimeMS', null], ['enableTelemetry', false]] as any)); }); it('rejects setting all config keys', async() => { diff --git a/packages/shell-api/src/shell-internal-state.spec.ts b/packages/shell-api/src/shell-internal-state.spec.ts index 222a1d326..d830fc143 100644 --- a/packages/shell-api/src/shell-internal-state.spec.ts +++ b/packages/shell-api/src/shell-internal-state.spec.ts @@ -47,10 +47,12 @@ describe('ShellInternalState', () => { expect(() => run('db = 42')).to.throw("[COMMON-10002] Cannot reassign 'db' to non-Database type"); }); - it('allows setting db to a db and causes prefetching', () => { + it('allows setting db to a db and causes prefetching', async() => { serviceProvider.listCollections .resolves([ { name: 'coll1' }, { name: 'coll2' } ]); expect(run('db = db.getSiblingDB("moo"); db.getName()')).to.equal('moo'); + await new Promise(setImmediate); + await new Promise(setImmediate); // ticks due to db._baseOptions() being async expect(serviceProvider.listCollections.calledWith('moo', {}, { readPreference: 'primaryPreferred', nameOnly: true diff --git a/packages/types/src/index.spec.ts b/packages/types/src/index.spec.ts index f7a80c224..cb5ee883c 100644 --- a/packages/types/src/index.spec.ts +++ b/packages/types/src/index.spec.ts @@ -31,6 +31,11 @@ describe('config validation', () => { expect(await validate('displayBatchSize', 0)).to.equal('displayBatchSize must be a positive integer'); expect(await validate('displayBatchSize', 1)).to.equal(null); expect(await validate('displayBatchSize', Infinity)).to.equal(null); + expect(await validate('maxTimeMS', 'foo')).to.equal('maxTimeMS must be null or a positive integer'); + expect(await validate('maxTimeMS', -1)).to.equal('maxTimeMS must be null or a positive integer'); + expect(await validate('maxTimeMS', 0)).to.equal('maxTimeMS must be null or a positive integer'); + expect(await validate('maxTimeMS', 1)).to.equal(null); + expect(await validate('maxTimeMS', null)).to.equal(null); expect(await validate('enableTelemetry', 'foo')).to.equal('enableTelemetry must be a boolean'); expect(await validate('enableTelemetry', -1)).to.equal('enableTelemetry must be a boolean'); expect(await validate('enableTelemetry', false)).to.equal(null); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2e2f60d94..683b18b10 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -343,6 +343,7 @@ export interface MongoshBus { export class ShellUserConfig { displayBatchSize = 20; + maxTimeMS: number | null = null; enableTelemetry = false; } @@ -355,6 +356,11 @@ export class ShellUserConfigValidator { return `${key} must be a positive integer`; } return null; + case 'maxTimeMS': + if (value !== null && (typeof value !== 'number' || value <= 0)) { + return `${key} must be null or a positive integer`; + } + return null; case 'enableTelemetry': if (typeof value !== 'boolean') { return `${key} must be a boolean`;