Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d4ad59e

Browse files
committed
Clear the copy of the globs dict after running examples. This helps to
break cycles, which are a special problem when running generator tests that provoke exceptions by invoking the .next() method of a named generator-iterator: then the iterator is named in globs, and the iterator's frame gets a tracekback object pointing back to globs, and gc doesn't chase these types so the cycle leaks. Also changed _run_examples() to make a copy of globs itself, so its callers (direct and indirect) don't have to (and changed the callers to stop making their own copies); *that* much is a change I've been meaning to make for a long time (it's more robust the new way). Here's a way to provoke the symptom without doctest; it leaks at a prodigious rate; if the last two "source" lines are replaced with g().next() the iterator isn't named and then there's no leak: source = """\ def g(): yield 1/0 k = g() k.next() """ code = compile(source, "<source>", "exec") def f(globs): try: exec code in globs except ZeroDivisionError: pass while 1: f(globals().copy()) After this change, running test_generators in an infinite loop still leaks, but reduced from a flood to a trickle.
1 parent 77f2d50 commit d4ad59e

1 file changed

Lines changed: 15 additions & 5 deletions

File tree

Lib/doctest.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -529,23 +529,34 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name):
529529

530530
return failures, len(examples)
531531

532-
# Run list of examples, in context globs. Return (#failures, #tries).
532+
# Run list of examples, in a shallow copy of context (dict) globs.
533+
# Return (#failures, #tries).
534+
# CAUTION: globs is cleared before returning. This is to help break
535+
# cycles that may have been created by the examples.
533536

534537
def _run_examples(examples, globs, verbose, name):
535538
import sys
536539
saveout = sys.stdout
540+
globs = globs.copy()
537541
try:
538542
sys.stdout = fakeout = _SpoofOut()
539543
x = _run_examples_inner(saveout.write, fakeout, examples,
540544
globs, verbose, name)
541545
finally:
542546
sys.stdout = saveout
547+
# While Python gc can clean up most cycles on its own, it doesn't
548+
# chase frame objects. This is especially irksome when running
549+
# generator tests that raise exceptions, because a named generator-
550+
# iterator gets an entry in globs, and the generator-iterator
551+
# object's frame's traceback info points back to globs. This is
552+
# easy to break just by clearing the namespace.
553+
globs.clear()
543554
return x
544555

545556
def run_docstring_examples(f, globs, verbose=0, name="NoName"):
546557
"""f, globs, verbose=0, name="NoName" -> run examples from f.__doc__.
547558
548-
Use dict globs as the globals for execution.
559+
Use (a shallow copy of) dict globs as the globals for execution.
549560
Return (#failures, #tries).
550561
551562
If optional arg verbose is true, print stuff even if there are no
@@ -735,7 +746,7 @@ def runstring(self, s, name):
735746
f = t = 0
736747
e = _extract_examples(s)
737748
if e:
738-
f, t = _run_examples(e, self.globs.copy(), self.verbose, name)
749+
f, t = _run_examples(e, self.globs, self.verbose, name)
739750
if self.verbose:
740751
print f, "of", t, "examples failed in string", name
741752
self.__record_outcome(name, f, t)
@@ -773,8 +784,7 @@ def rundoc(self, object, name=None):
773784
"when object.__name__ doesn't exist; " + `object`)
774785
if self.verbose:
775786
print "Running", name + ".__doc__"
776-
f, t = run_docstring_examples(object, self.globs.copy(),
777-
self.verbose, name)
787+
f, t = run_docstring_examples(object, self.globs, self.verbose, name)
778788
if self.verbose:
779789
print f, "of", t, "examples failed in", name + ".__doc__"
780790
self.__record_outcome(name, f, t)

0 commit comments

Comments
 (0)