A variant of this solution is now committed. Please refresh your repo.
Things should be much better. (However you may want to call
gc.collect() occasionally -- see
http://code.google.com/p/tulip/issues/detail?id=42 )
Aymeric,
I have a tentative solution. If you patch
https://codereview.appspot.com/9057046/ into your copy of Tulip you
should see a traceback logged whenever a task or future gets an
exception and there are no callbacks.
We should probably put cg.collect() in all our tearDown() methods.
On Sat, May 4, 2013 at 4:33 PM, Glyph <gl...@twistedmatrix.com> wrote:
On May 4, 2013, at 7:51 AM, Guido van Rossum <gu...@python.org> wrote:We should probably put cg.collect() in all our tearDown() methods.
This is why Twisted's 'trial' testing tool has the `trial --force-gc´
command-line option, and the Twisted project has one build-slave that always
runs with that option set. Turning it on all the time is way too slow. I
would strongly caution you about hard-coding it in tearDown.
Hm. You're right. It seems to nearly triple the time to run the
current suite of Tulip unit tests. Of course even at 21 seconds it's
pretty spiffy, but I'm sure it'll slow down over time.
(OTOH there are still some tests that use real sleeps which we should mock out.)
For the tests that really care about this - and want to assert things about
tracebacks being logged - the thing to do is to call gc.collect() at a
particular moment in the test, and then verify that the logging happened
after the fact.
Good point.
What do you think of having something in the tulip event loop that
calls gc.collect() every once in a while, e.g. whenever it's been more
than 0.1 second since the last time it's been called?
(Actually, if someone doesn't mind answering a somewhat dumb question, how
does one assert something about logged errors with Python's stdlib logging?
This is a first-class feature in twisted.python.log but I've never run
across test cases that look at logged rather than raised exceptions with the
logging module. Hopefully this is just my ignorance speaking, though.)
Good question. In Tulip we use the unittest.mock package to mock out
the logging calls. In Python 2 I recall setting a handler that writes
to a known cStringIO instance, and then checking what got written
there. IIRC the hacky part there was to restore the old situation --
you have to save and restore a _handlers attribute on the Logger, or
something like that. (TBH the stdlib logging API drives me nuts
because it's so flexible -- there's always another level of
indirection, and I can never keep the difference straight between
loggers, handlers, formatters, or remember where the default
initialization is done, or why it sometimes *isn't* done. :-( )
On May 4, 2013, at 4:51 PM, Guido van Rossum <gu...@python.org> wrote:On Sat, May 4, 2013 at 4:33 PM, Glyph <gl...@twistedmatrix.com> wrote:
On May 4, 2013, at 7:51 AM, Guido van Rossum <gu...@python.org> wrote:We should probably put cg.collect() in all our tearDown() methods.
This is why Twisted's 'trial' testing tool has the `trial --force-gc´
command-line option, and the Twisted project has one build-slave that always
runs with that option set. Turning it on all the time is way too slow. I
would strongly caution you about hard-coding it in tearDown.
Hm. You're right. It seems to nearly triple the time to run the
current suite of Tulip unit tests. Of course even at 21 seconds it's
pretty spiffy, but I'm sure it'll slow down over time.More importantly, every application that *uses* Tulip is going to need to be able to have this functionality without slowing down *its* test suite. The problem only gets worse over time :).
(OTOH there are still some tests that use real sleeps which we should mock out.)Perhaps worth considering this, then:<https://twistedmatrix.com/documents/current/core/howto/trial.html#auto9>
For the tests that really care about this - and want to assert things about
tracebacks being logged - the thing to do is to call gc.collect() at a
particular moment in the test, and then verify that the logging happened
after the fact.
Good point.
What do you think of having something in the tulip event loop that
calls gc.collect() every once in a while, e.g. whenever it's been more
than 0.1 second since the last time it's been called?I think the way that trial does it is the best way to do it :). The test runner should have an option, or the test suite should have a configuration file. Either you've localized the failure to an individual test or not. If not, then it doesn't matter if it comes 100 tests later or 1000 tests later.
(Actually, if someone doesn't mind answering a somewhat dumb question, how
does one assert something about logged errors with Python's stdlib logging?
This is a first-class feature in twisted.python.log but I've never run
across test cases that look at logged rather than raised exceptions with the
logging module. Hopefully this is just my ignorance speaking, though.)
Good question. In Tulip we use the unittest.mock package to mock out
the logging calls. In Python 2 I recall setting a handler that writes
to a known cStringIO instance, and then checking what got written
there. IIRC the hacky part there was to restore the old situation --
you have to save and restore a _handlers attribute on the Logger, or
something like that. (TBH the stdlib logging API drives me nuts
because it's so flexible -- there's always another level of
indirection, and I can never keep the difference straight between
loggers, handlers, formatters, or remember where the default
initialization is done, or why it sometimes *isn't* done. :-( )
Modifying the logging module itself to better support testing might be a good idea; or, at least, publicly exposing something for constructing a properly-shaped mock and then asserting useful things about it.
On Sat, May 4, 2013 at 5:25 PM, Glyph <gl...@twistedmatrix.com> wrote:On May 4, 2013, at 4:51 PM, Guido van Rossum <gu...@python.org> wrote:On Sat, May 4, 2013 at 4:33 PM, Glyph <gl...@twistedmatrix.com> wrote:
On May 4, 2013, at 7:51 AM, Guido van Rossum <gu...@python.org> wrote:We should probably put cg.collect() in all our tearDown() methods.
This is why Twisted's 'trial' testing tool has the `trial --force-gc´
command-line option, and the Twisted project has one build-slave that always
runs with that option set. Turning it on all the time is way too slow. I
would strongly caution you about hard-coding it in tearDown.
Hm. You're right. It seems to nearly triple the time to run the
current suite of Tulip unit tests. Of course even at 21 seconds it's
pretty spiffy, but I'm sure it'll slow down over time.More importantly, every application that *uses* Tulip is going to need to be able to have this functionality without slowing down *its* test suite. The problem only gets worse over time :).
At this point I've lost track of what you mean by "this functionality". :-( Are we talking about an option to call gc.collect() in Tulip's event loop or in tearDown()?
(OTOH there are still some tests that use real sleeps which we should mock out.)Perhaps worth considering this, then:<https://twistedmatrix.com/documents/current/core/howto/trial.html#auto9>
That doesn't do enough -- we don't really do *real* real sleeps, we call into the selectors/proactors with certain timeouts set. So we'd have to mock out select(), poll() and friends and their proactor equivalents. Possible, but your clock abstraction doesn't really help here.
PS. That URL gives me a warning about an invalid certificate.
For the tests that really care about this - and want to assert things about
tracebacks being logged - the thing to do is to call gc.collect() at a
particular moment in the test, and then verify that the logging happened
after the fact.
Good point.
What do you think of having something in the tulip event loop that
calls gc.collect() every once in a while, e.g. whenever it's been more
than 0.1 second since the last time it's been called?I think the way that trial does it is the best way to do it :). The test runner should have an option, or the test suite should have a configuration file. Either you've localized the failure to an individual test or not. If not, then it doesn't matter if it comes 100 tests later or 1000 tests later.
I really, really, really do not want to write my own test runner, so I've passed this on to the owners of the stdlib unittest package (http://bugs.python.org/issue17908).
However, I wasn't talking about tests. I was talking about production (or at least running code outside a test runner). Assuming that not all bugs are found during testing, it may be useful to have garbage collection happening somewhat regularly (without using something like call_repeatedly() to call it every second, since it shouldn't happen if we're just waiting a really long time for the next I/O event). But it sounds like you either haven't thought about it or you think it's a really bad idea (in which case, no need to reply :-).
(Actually, if someone doesn't mind answering a somewhat dumb question, how
does one assert something about logged errors with Python's stdlib logging?
This is a first-class feature in twisted.python.log but I've never run
across test cases that look at logged rather than raised exceptions with the
logging module. Hopefully this is just my ignorance speaking, though.)
Good question. In Tulip we use the unittest.mock package to mock out
the logging calls. In Python 2 I recall setting a handler that writes
to a known cStringIO instance, and then checking what got written
there. IIRC the hacky part there was to restore the old situation --
you have to save and restore a _handlers attribute on the Logger, or
something like that. (TBH the stdlib logging API drives me nuts
because it's so flexible -- there's always another level of
indirection, and I can never keep the difference straight between
loggers, handlers, formatters, or remember where the default
initialization is done, or why it sometimes *isn't* done. :-( )Modifying the logging module itself to better support testing might be a good idea; or, at least, publicly exposing something for constructing a properly-shaped mock and then asserting useful things about it.
I don't see the need -- the mocks we use in Tulip are really easy already.
The plot thickens. If I for exception.__traceback__ to None in
Future.set_exception(), the cycle is broken. (This is the only change
necessary.) I think that there is some state in a frame that keeps the
cycle alive, but which is not accessible through a public attribute.
I'm hesitant to make this change, because it means that user code
won't be able to inspect the traceback; but it is very tempting. :-)