-
Notifications
You must be signed in to change notification settings - Fork 39
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
Handle opt.destroy() being a promise #16
Conversation
(the node 8 test failure seems unrelated) |
Did you handle all the cases where |
Also now what happens with synchronous errors (a log message) and what happens with async errors (leaked error) is different. Those should probably be handle the same way. |
The other uses seem to be triggered
So I left those as they are - I think it's fine actually. If not, a larger rethink is probably necessary. What do you think?
I don't think that's the case in line 158. If a sync error happens, it's caught by the .map, which passes it to the promise.all, which passes it to the .then. Similarly, if there is an async error, it gets re-thrown by promise.all, which passes it to the .then. |
Errors are not passed from Write the tests and you'll see. This won't get merged until you test all the cases with both failing handlers and working handlers. |
Alright, have a look and let me know what you think |
Any feedback? |
Sorry, I'm on vacation for three weeks without my laptop. I'll get back to you after that. |
hey @koskimas I hope you had a great vacation - any chance you could take a look at this PR again? |
I did, but now I'm swamped with work and with life in general. I just don't have time for this project. You can try any of the other contributors. |
lib/Pool.js
Outdated
// There's nothing we can do here but log the error. | ||
retVal.catch(this._logError.bind(this)); | ||
} | ||
return retVal; |
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 part I don't understand. Assuming destroyer
is a promise now returning the value like this makes the try-catch pretty redundant since it's not 'awaited' for.
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.
Isn't this equivalent to the current catch behaviour? While we don't await for it, I guess we still want to log it to not hide it from the user (or at least that's why I assume we're logging the error for the sync destroyer
)
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.
What I mean is calling try/catch on a promise without async/await is redudant. Error is caught in .catch handler instead. Can remove try catch block completely.
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.
it's meant to catch caused by this.destroyer
when it's synchronous and not a promise - since that's the old behaviour. Should I make a comment into the catch block to make that clearer?
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.
Since destroy is returning promise now shouldn't it convert that catched error to caller as return Promise.reject(err)
? Also I'm not sure at all if _destroy() should catch all error internally anymore... for this use case to wait that pool is completely torn down it probably doesn't matter.
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'm happy to make that change - I have no insight into why the error was silenced in the sync case. could you as the maintainers decide what you'd like 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.
For now, I will keep the current behaviour - i.e., hide errors & always resolve
Let me know if you want to change that
lib/Pool.js
Outdated
@@ -155,7 +155,9 @@ class Pool { | |||
}) | |||
.then(() => { | |||
// Now we can destroy all the freed resources. | |||
this.free.forEach(free => this._destroy(free.resource)); | |||
return Promise.all(this.free.map(free => this._destroy(free.resource))); |
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 understand the point of this, but I think compared to how Tarn used to handle this, it is now potentially flawed since:
- There's no timeout set for destroy, it could hang forever in worst case
- It doesn't clear current state after potential error occurs which it did before
This could potentially leave the tarn instance in a state where it has 'tried' to destroy all active resources - but failed - and there's no way to create new resources because it is filled with faulty resources.
I could be thinking too far into it and may be mistaken, but this is my interpretation of this change..
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.
Regarding 1., how about we use https://github.com/sindresorhus/p-timeout ?
Regarding 2., sorry I don't understand - how/where does it currently clear state that it doesn't anymore with these changes?
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 Promise.all
rejects right away if one of the destroy commands fail. So there might be 200 destroys running at the same time, first one rejects and Promise.all
rejects right away, even that there are 199 promises still unresolved. (see for example this https://stackoverflow.com/questions/31424561/wait-until-all-es6-promises-complete-even-rejected-promises)
EDIT: sorry, I just expanded some code above this. Looks like reflect stuff is already implemented there 👍 so yeah this comment was just wrong and can be ignored.
EDIT2: ... except look like actually reflect should be used here too, because otherwise this.free = [];
is set too early and we return too early...
EDIT3: Since _destroy() seems to always catch all errors internally, I suppose that currently _destroy actually never rejects... anyhow it would be safer to use reflect anyways in case if functionality of _destroy is changed at some point.
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.
Regarding 1., how about we use https://github.com/sindresorhus/p-timeout ?
Since tarn.js actually does have also own timeout for acquire and create already, it would make sense to have it for destroy. No need to include any additional deps for it.
Regarding 2., sorry I don't understand - how/where does it currently clear state that it doesn't anymore with these changes?
Since _destroy never rejects, I think that functionality is still the same. Anyways reflect should be added there just to be on safe side for future, so that rejected promises will not cause Promise.all to reject too early.
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.
Added that as well as an destroyTimeoutMillis
option
cedf7ad
to
78903bd
Compare
Made some more changes - looking forward to your input! |
README.md
Outdated
@@ -55,6 +55,10 @@ const pool = new Pool({ | |||
// if a resource cannot be acquired | |||
createTimeoutMillis: 30000, | |||
|
|||
// destroy operations are awaited for at most this many milliseconds | |||
// new resources will be created after this timeout | |||
destroyTimeoutMillis: 200, |
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 sounds way too small timeout, better to wait few seconds to be sure. I wouldn't think that waiting for example 5 seconds in case of weird problems here would be a problem. Better to wait a bit longer than timeout too soon, before destroy is ready.
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.
should be changed to be 5000 here too to be consistent
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.
Looks really good, I didn't find anything to comment of implementation, except that destroy default timeout could be longer. I think it is better to have to wait up couple of seconds in case of hanging destroy instead of accidentally calling timeout and returning when pool has not yet released all the resources.
Also maybe I missed something, but I didn't see that any test was making sure that destroy timeout prevents destroy call from hanging. After those changes I think this is good 👍
I increased the timeout and added that test - let me know what you think |
@elhigu could you take another look if you think it's fine now? |
src/Pool.ts
Outdated
}); | ||
|
||
// In case of an error there's nothing we can do here but log it. | ||
return pendingDestroy.promise.catch(this._logError.bind(this)); |
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 this would be more clear without bind like this:
return pendingDestroy.promise.catch(err => this._logError(err));
@alubbe sorry for delay, I still managed to find two minor issues (in readme and using .bind() when it is not necessary). If you can fix them I'll merge this ASAP. Thanks for your patience! |
5ab8a21
to
2d2c460
Compare
done! :) |
Thank you very much! I'm not entirely sure if this is a breaking change or not... I would say its not and just bump minor version. Any opinions? |
I agree - the docs expect a sync destroyer, for which the behaviour is unchanged. And for consumers like knex, who ended up returning a promise, this seemed to be working, so I it feels unlikely this feature could breakt it |
knex.js passes a function as
opt.destroy
that creates a promise, so I believe the.destroy()
should await completion. My PR works the same as previously ifopt.destroy
does not create a promise.This was found via bluebird highlighting this promise creation.