Skip to content

Commit e9dfb68

Browse files
authored
Add support for maxTimeMS mongoOption (#3018)
1 parent fbbc237 commit e9dfb68

File tree

3 files changed

+67
-10
lines changed

3 files changed

+67
-10
lines changed

spec/MongoStorageAdapter.spec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,43 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
6060
});
6161
});
6262

63+
it('find succeeds when query is within maxTimeMS', (done) => {
64+
const maxTimeMS = 250;
65+
let adapter = new MongoStorageAdapter({
66+
uri: databaseURI,
67+
mongoOptions: { maxTimeMS },
68+
});
69+
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
70+
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS / 2})` }))
71+
.then(
72+
() => done(),
73+
(err) => {
74+
done.fail(`maxTimeMS should not affect fast queries ${err}`);
75+
}
76+
);
77+
})
78+
79+
it('find fails when query exceeds maxTimeMS', (done) => {
80+
const maxTimeMS = 250;
81+
let adapter = new MongoStorageAdapter({
82+
uri: databaseURI,
83+
mongoOptions: { maxTimeMS },
84+
});
85+
adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
86+
.then(() => adapter._rawFind('Foo', { '$where': `sleep(${maxTimeMS * 2})` }))
87+
.then(
88+
() => {
89+
done.fail('Find succeeded despite taking too long!');
90+
},
91+
(err) => {
92+
expect(err.name).toEqual('MongoError');
93+
expect(err.code).toEqual(50);
94+
expect(err.message).toEqual('operation exceeded time limit');
95+
done();
96+
}
97+
);
98+
});
99+
63100
it('stores pointers with a _p_ prefix', (done) => {
64101
let obj = {
65102
objectId: 'bar',

src/Adapters/Storage/Mongo/MongoCollection.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export default class MongoCollection {
1313
// none, then build the geoindex.
1414
// This could be improved a lot but it's not clear if that's a good
1515
// idea. Or even if this behavior is a good idea.
16-
find(query, { skip, limit, sort, keys } = {}) {
17-
return this._rawFind(query, { skip, limit, sort, keys })
16+
find(query, { skip, limit, sort, keys, maxTimeMS } = {}) {
17+
return this._rawFind(query, { skip, limit, sort, keys, maxTimeMS })
1818
.catch(error => {
1919
// Check for "no geoindex" error
2020
if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) {
@@ -30,22 +30,29 @@ export default class MongoCollection {
3030
index[key] = '2d';
3131
return this._mongoCollection.createIndex(index)
3232
// Retry, but just once.
33-
.then(() => this._rawFind(query, { skip, limit, sort, keys }));
33+
.then(() => this._rawFind(query, { skip, limit, sort, keys, maxTimeMS }));
3434
});
3535
}
3636

37-
_rawFind(query, { skip, limit, sort, keys } = {}) {
37+
_rawFind(query, { skip, limit, sort, keys, maxTimeMS } = {}) {
3838
let findOperation = this._mongoCollection
3939
.find(query, { skip, limit, sort })
4040

4141
if (keys) {
4242
findOperation = findOperation.project(keys);
4343
}
44+
45+
if (maxTimeMS) {
46+
findOperation = findOperation.maxTimeMS(maxTimeMS);
47+
}
48+
4449
return findOperation.toArray();
4550
}
4651

47-
count(query, { skip, limit, sort } = {}) {
48-
return this._mongoCollection.count(query, { skip, limit, sort });
52+
count(query, { skip, limit, sort, maxTimeMS } = {}) {
53+
let countOperation = this._mongoCollection.count(query, { skip, limit, sort, maxTimeMS });
54+
55+
return countOperation;
4956
}
5057

5158
insertOne(object) {

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export class MongoStorageAdapter {
9292
this._uri = uri;
9393
this._collectionPrefix = collectionPrefix;
9494
this._mongoOptions = mongoOptions;
95+
96+
// MaxTimeMS is not a global MongoDB client option, it is applied per operation.
97+
this._maxTimeMS = mongoOptions.maxTimeMS;
9598
}
9699

97100
connect() {
@@ -329,7 +332,13 @@ export class MongoStorageAdapter {
329332
return memo;
330333
}, {});
331334
return this._adaptiveCollection(className)
332-
.then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort, keys: mongoKeys }))
335+
.then(collection => collection.find(mongoWhere, {
336+
skip,
337+
limit,
338+
sort: mongoSort,
339+
keys: mongoKeys,
340+
maxTimeMS: this._maxTimeMS,
341+
}))
333342
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
334343
}
335344

@@ -358,14 +367,18 @@ export class MongoStorageAdapter {
358367

359368
// Used in tests
360369
_rawFind(className, query) {
361-
return this._adaptiveCollection(className).then(collection => collection.find(query));
370+
return this._adaptiveCollection(className).then(collection => collection.find(query, {
371+
maxTimeMS: this._maxTimeMS,
372+
}));
362373
}
363374

364-
// Executs a count.
375+
// Executes a count.
365376
count(className, schema, query) {
366377
schema = convertParseSchemaToMongoSchema(schema);
367378
return this._adaptiveCollection(className)
368-
.then(collection => collection.count(transformWhere(className, query, schema)));
379+
.then(collection => collection.count(transformWhere(className, query, schema), {
380+
maxTimeMS: this._maxTimeMS,
381+
}));
369382
}
370383

371384
performInitialization() {

0 commit comments

Comments
 (0)