-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Tie Runner lifetime to lifetime of result future or coroutine function (whichever is GCed first) #1782
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
Conversation
|
Looks like my |
|
It looks like implementing the I can continue to clean up instances like these but first I'd like a Tornado developer to tell me if I'm on the right path with this or if I should just leave the |
It doesn't make sense for the I think I'd make the I'm concerned about the performance impact of a |
|
I agree with your points. I'll remove the I agree that we should test the performance. Does Tornado have any existing performance benchmarking suites we can use for testing performance after my changes? |
|
We don't have great benchmarks. What we do have is in |
|
I ran the performance benchmarks and I saw no differences between my implementation and the previous implementation. The tests were highly variable, I ran each multiple times and they would vary between 1100 req/sec and 1600 req/sec. |
|
I believe this changeset addresses all of your concerns @bdarnell , let me know if you'd like to see any other adjustments. |
|
|
||
|
|
||
| # Ties lifetime of futures to their runners. Github Issue #1769 | ||
| _futures_to_runners = weakref.WeakKeyDictionary() |
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.
Add a blank line after this definition, and expand the comment. Something like:
Unlike regular stack frames, idle coroutines are not anchored to a garbage-collection root. Object references point in the opposite direction from what you'd expect, with child frames holding references to the parent frames that they will awaken when completed, but there is not necessarily anything keeping the child frames alive (In most cases the innermost child frame will be registered as a callback on the IOLoop, but other patterns are possible especially when weak references are used). This map provides hard references in the other direction, so that as long as the Future returned by a coroutine is referenced (typically by the parent's Runner), this coroutine's Runner will be kept alive.
|
Thanks, looks good. But in addition to the comment above, please squash the two commits into one. It's good that you wrote the test first to make sure it's sensitive enough to detect the problem, but I'd rather not have commits that are known to fail their tests in the history, since they make bisecting difficult. |
|
Okay, sounds good to me. I've squashed the commits and added some comments detailing the problem we've solved to gen.py. |
|
Thanks! |
This changeset fixes #1769 to my satisfaction (and I hope everyone else's).
My changeset causes the
gen.coroutine(andgen.engine, I think) to keep aWeakKeyDictionarymapping result futures toRunnerobjects. ThisWeakKeyDictionaryisclosured to the coroutine generator function itselfa global.The resulting behavior is that, if there exists hard references to the result future object
and to the coroutine function, theRunnerobject will not die.Everything below is no longer relevant but I'm keeping it for historical reasons
In addition, I added a
__del__method to theRunnerobject (In Python > 3.4 only) which causes it to "cancel" (load with an exception, since tornado's futures don't have cancel semantics) the result future if theRunneris collected before it is completed. This prevents applications from hanging forever waiting on futures that will never complete.Things I'm still concerned about:
__del__since it could cause "working" code to start spamming "future exception never cleaned up" notices if they somehow made a habit out of getting theirRunner's collected. It is difficult to imagine a situation whereRunners being garbage collected before the future has a result being a desired behavior, though.WaitIteratorwhere multiple futures' results are tied together (would their runners get GCed and give weird exceptions in the log instead of finishing?). But the unit tests pass so what could go wrong right?_GC_CYCLE_FINALIZERSfromtornado.concurrent. Maybe the symbol should have been imported instead, but the fact it is "private" made me a bit nervous about that.