-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(changeStream): properly handle changeStream event mid-close #1902
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1677,4 +1677,133 @@ describe('Change Streams', function() { | |
.then(() => finish(), err => finish(err)); | ||
} | ||
}); | ||
|
||
describe('should properly handle a changeStream event being processed mid-close', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to refactor these three tests to use the same setup/teardown code for a client, rather than clutter the tests with it? It also feels like you can use the same method to write data, as you're really only testing different APIs for reads here |
||
it('when invoked with promises', { | ||
metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, | ||
test: function() { | ||
function read(coll) { | ||
const changeStream = coll.watch(); | ||
return Promise.resolve() | ||
.then(() => changeStream.next()) | ||
.then(() => changeStream.next()) | ||
.then(() => { | ||
const closeP = Promise.resolve().then(() => changeStream.close()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually a relic of when I was trying to enqueue |
||
const nextP = changeStream.next(); | ||
|
||
return closeP.then(() => nextP); | ||
}); | ||
} | ||
|
||
function write(coll) { | ||
return Promise.resolve() | ||
.then(() => coll.insertOne({ a: 1 })) | ||
.then(() => coll.insertOne({ b: 2 })) | ||
.then(() => coll.insertOne({ c: 3 })); | ||
} | ||
|
||
const client = this.configuration.newClient(); | ||
|
||
return client.connect().then(() => { | ||
const coll = client.db(this.configuration.db).collection('tester'); | ||
|
||
return Promise.all([read(coll), write(coll)]) | ||
.then( | ||
() => Promise.reject(new Error('Expected operation to fail with error')), | ||
err => expect(err.message).to.equal('ChangeStream is closed') | ||
) | ||
.then(() => client.close(), err => client.close().then(() => Promise.reject(err))); | ||
}); | ||
} | ||
}); | ||
|
||
it('when invoked with callbacks', { | ||
metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, | ||
test: function(done) { | ||
const client = this.configuration.newClient(); | ||
let closed = false; | ||
const close = err => { | ||
if (closed) { | ||
return; | ||
} | ||
closed = true; | ||
return client.close(() => done(err)); | ||
}; | ||
client.connect(err => { | ||
if (err) { | ||
return close(err); | ||
} | ||
|
||
const coll = client.db(this.configuration.db).collection('tester'); | ||
const changeStream = coll.watch(); | ||
|
||
changeStream.next(() => { | ||
changeStream.next(() => { | ||
changeStream.next(err => { | ||
let _err = null; | ||
try { | ||
expect(err.message).to.equal('ChangeStream is closed'); | ||
} catch (e) { | ||
_err = e; | ||
} finally { | ||
close(_err); | ||
} | ||
}); | ||
changeStream.close(); | ||
}); | ||
}); | ||
|
||
coll.insertOne({ a: 1 }, () => { | ||
coll.insertOne({ b: 2 }, () => { | ||
coll.insertOne({ c: 3 }, () => {}); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
|
||
it('when invoked using eventEmitter API', { | ||
metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, | ||
test: function(done) { | ||
const client = this.configuration.newClient(); | ||
let closed = false; | ||
const close = _err => { | ||
if (closed) { | ||
return; | ||
} | ||
closed = true; | ||
return client.close(() => done(_err)); | ||
}; | ||
|
||
client.connect(err => { | ||
if (err) { | ||
return close(err); | ||
} | ||
|
||
const coll = client.db(this.configuration.db).collection('tester'); | ||
const changeStream = coll.watch(); | ||
|
||
let counter = 0; | ||
changeStream.on('change', () => { | ||
counter += 1; | ||
if (counter === 2) { | ||
changeStream.close(); | ||
setTimeout(() => close()); | ||
} else if (counter >= 3) { | ||
close(new Error('Should not have received more than 2 events')); | ||
} | ||
}); | ||
changeStream.on('error', err => close(err)); | ||
|
||
setTimeout(() => { | ||
coll.insertOne({ a: 1 }, () => { | ||
coll.insertOne({ b: 2 }, () => { | ||
coll.insertOne({ c: 3 }, () => {}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be a
MongoError
right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also nit, but should
ChangeStream
just bechange stream
? It makes it look like a product name here 😄There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was referring to the
class ChangeStream
here. Figured it was better to specify the constructor object.Also, I specifically resisted throwing a
MongoError
b/c right now we useMongoError
mostly for server-side errors. We don't really have a concept of aMongoDriverError
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 on point one
For the second point, I still think it makes sense to mark it as a
MongoError
until such an error class exists. It makes it harder to catch errors generically limited to the driver if we also sometimes throw plainError
s, at very least even if/when aMongoDriverError
is introduced it will be a subclass ofMongoError