-
Notifications
You must be signed in to change notification settings - Fork 148
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: Support more than 100 long-lived streams #623
Conversation
f8404db
to
4d5250a
Compare
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 like the high level way this works better than the way I did it.
dev/src/index.ts
Outdated
@@ -1176,7 +1174,7 @@ export class Firestore { | |||
let endCalled = false; | |||
|
|||
return new Promise((resolve, reject) => { | |||
const streamReady = () => { | |||
const releaseStream = () => { |
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 don't like this name. "released" can be confused with releasing back into the pool. I certainly was confused that way when reading this.
This promise represents that the stream is ready for use or initialized or something like that.
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 changed the PR to keep the renames of your original commit.
dev/src/index.ts
Outdated
}); | ||
return this._initializeStream(resultStream, requestTag, request); | ||
}) | ||
.then(stream => { |
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.
async/await?
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 cleaned this up. I also removed the return value from _initializeStream as part of this since we always return the unmodified resultStream.
Codecov Report
@@ Coverage Diff @@
## master #623 +/- ##
==========================================
+ Coverage 61.45% 61.47% +0.02%
==========================================
Files 21 21
Lines 3409 3411 +2
Branches 460 459 -1
==========================================
+ Hits 2095 2097 +2
Misses 1252 1252
Partials 62 62
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #623 +/- ##
==========================================
+ Coverage 61.52% 61.68% +0.16%
==========================================
Files 21 21
Lines 3407 3422 +15
Branches 460 459 -1
==========================================
+ Hits 2096 2111 +15
Misses 1250 1250
Partials 61 61
Continue to review full report at Codecov.
|
}); | ||
|
||
const resultStream = bun([stream, logStream]); | ||
resultStream.on('close', lifetime.resolve); |
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.
Are streams guaranteed to emit the close
event? What happens in the case of an error?
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.
According to https://nodejs.org/api/stream.html, yes:
"A Writable stream will always emit the 'close' event if it is created with the emitClose option."
"A Readable stream will always emit the 'close' event if it is created with the emitClose option."
emitClose
defaults to true.
I originally trusted this, but I spent more time and added test asserts. It turns out that the close
event is not always emitted. To make the unit and system tests pass, I also have to wait for error/end and finish on writeable streams.
await Promise.all(emptyResults.map(d => d.promise)); | ||
ref.set({i: 1337}); | ||
await Promise.all(documentResults.map(d => d.promise)); | ||
unsubscribeCallbacks.forEach(c => c()); |
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.
This test verifies that all 150 listeners succeed but doesn't verify that everything has been properly released to the pool.
Is it possible to check that pool.size is 150 once the listeners are started and then get back to zero after?
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 worry that this test can succeed even if you remove the line that resolves the lifetime promise.
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 added a "shutdown" block to each tests that verifies that the operation count goes back to zero. I had to change some of the unit tests to make this work.
This is now failing tests under node 8:
LGTM otherwise. |
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.
LGTM. Awesome!
The test failure for Node 8 was due to a flake, which I fixed. |
@wilhuff @schmidt-sebastian I just upgraded to I read through the release notes and found that this thread is probably the reason that I am seeing these warnings. I tried downgrading back to What are your guys' thoughts? |
@jakeleventhal Thanks for letting us know! I think I have seen this warning once before as well, so it may indeed be related to this change (which adds a lot of listeners). I filed #661 to track this. |
Fixes firebase/firebase-admin-node#499
This is a different approach to #614 and solves the same problem. It re-uses more of the original code to achieve the same result. Instead of releasing the client back to the pool when a stream is marked as established, we instead keep it around until the stream is closed.