]> granicus.if.org Git - python/commitdiff
Issue #17934: Add a clear() method to frame objects, to help clean up expensive detai...
authorAntoine Pitrou <solipsis@pitrou.net>
Mon, 5 Aug 2013 21:26:40 +0000 (23:26 +0200)
committerAntoine Pitrou <solipsis@pitrou.net>
Mon, 5 Aug 2013 21:26:40 +0000 (23:26 +0200)
Doc/library/inspect.rst
Doc/reference/datamodel.rst
Include/frameobject.h
Include/genobject.h
Lib/test/test_sys.py
Lib/test/test_traceback.py
Misc/NEWS
Objects/frameobject.c
Objects/genobject.c
Python/ceval.c

index af6c96b6c31690b881f6b7dae05bd64b75891ba1..9f784fd674e423d48d326e24813887a030d908be 100644 (file)
@@ -846,6 +846,10 @@ index of the current line within that list.
           finally:
               del frame
 
+   If you want to keep the frame around (for example to print a traceback
+   later), you can also break reference cycles by using the
+   :meth:`frame.clear` method.
+
 The optional *context* argument supported by most of these functions specifies
 the number of lines of context to return, which are centered around the current
 line.
index 95028c2b97b153fe5b527666a07934967bfd7838..a88d5623cf76b6ca48f091dbb60e438c7b1d4241 100644 (file)
@@ -934,6 +934,20 @@ Internal types
       frame).  A debugger can implement a Jump command (aka Set Next Statement)
       by writing to f_lineno.
 
+      Frame objects support one method:
+
+      .. method:: frame.clear()
+
+         This method clears all references to local variables held by the
+         frame.  Also, if the frame belonged to a generator, the generator
+         is finalized.  This helps break reference cycles involving frame
+         objects (for example when catching an exception and storing its
+         traceback for later use).
+
+         :exc:`RuntimeError` is raised if the frame is currently executing.
+
+         .. versionadded:: 3.4
+
    Traceback objects
       .. index::
          object: traceback
index 33f73af52a3c8b7145f24042d49309b558ac1788..10ba06fedb24f125784d2dbdb24db8af01ac7ac6 100644 (file)
@@ -36,6 +36,8 @@ typedef struct _frame {
            non-generator frames. See the save_exc_state and swap_exc_state
            functions in ceval.c for details of their use. */
     PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
+    /* Borrowed referenced to a generator, or NULL */
+    PyObject *f_gen;
 
     PyThreadState *f_tstate;
     int f_lasti;                /* Last instruction if called */
@@ -46,6 +48,7 @@ typedef struct _frame {
        bytecode index. */
     int f_lineno;               /* Current line number */
     int f_iblock;               /* index in f_blockstack */
+    char f_executing;           /* whether the frame is still executing */
     PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
     PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
 } PyFrameObject;
index ed451baf3cb0feb45184c4e03d91b10ed6a63ae8..65f1ecfd54958fd10b30ff9f68026573ab089f85 100644 (file)
@@ -36,6 +36,8 @@ PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
 PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
 PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
 PyObject *_PyGen_Send(PyGenObject *, PyObject *);
+PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
+
 
 #ifdef __cplusplus
 }
index 26c7ae7b6f2cdac3cd02b8f171f0dc53dfa6fc73..e31bbc2215ed033c1b97b90e754b047f5dda8906 100644 (file)
@@ -764,7 +764,7 @@ class SizeofTest(unittest.TestCase):
         nfrees = len(x.f_code.co_freevars)
         extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
                   ncells + nfrees - 1
-        check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
+        check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
         # function
         def func(): pass
         check(func, size('12P'))
index 24753a8321639d5d7b5467b06d1fa56efa593b34..66a12bf12c12c5621e4bad4cb1559370def9eff9 100644 (file)
@@ -150,11 +150,17 @@ class SyntaxTracebackCases(unittest.TestCase):
 
 class TracebackFormatTests(unittest.TestCase):
 
-    def test_traceback_format(self):
+    def some_exception(self):
+        raise KeyError('blah')
+
+    def check_traceback_format(self, cleanup_func=None):
         try:
-            raise KeyError('blah')
+            self.some_exception()
         except KeyError:
             type_, value, tb = sys.exc_info()
+            if cleanup_func is not None:
+                # Clear the inner frames, not this one
+                cleanup_func(tb.tb_next)
             traceback_fmt = 'Traceback (most recent call last):\n' + \
                             ''.join(traceback.format_tb(tb))
             file_ = StringIO()
@@ -183,12 +189,22 @@ class TracebackFormatTests(unittest.TestCase):
 
         # Make sure that the traceback is properly indented.
         tb_lines = python_fmt.splitlines()
-        self.assertEqual(len(tb_lines), 3)
-        banner, location, source_line = tb_lines
+        self.assertEqual(len(tb_lines), 5)
+        banner = tb_lines[0]
+        location, source_line = tb_lines[-2:]
         self.assertTrue(banner.startswith('Traceback'))
         self.assertTrue(location.startswith('  File'))
         self.assertTrue(source_line.startswith('    raise'))
 
+    def test_traceback_format(self):
+        self.check_traceback_format()
+
+    def test_traceback_format_with_cleared_frames(self):
+        # Check that traceback formatting also works with a clear()ed frame
+        def cleanup_tb(tb):
+            tb.tb_frame.clear()
+        self.check_traceback_format(cleanup_tb)
+
     def test_stack_format(self):
         # Verify _stack functions. Note we have to use _getframe(1) to
         # compare them without this frame appearing in the output
index 9f2c2dd3c681801e90cd65c3a3cd2b5095b708c0..b33b94e6c6ddfe1297d8de93ea0a6f92decbe015 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ Projected Release date: 2013-09-08
 Core and Builtins
 -----------------
 
+- Issue #17934: Add a clear() method to frame objects, to help clean up
+  expensive details (local variables) and break reference cycles.
+
 Library
 -------
 
index d3b59f1ea6c99cdb504e368aad724cce326cec23..a62a45e1f6035c60ee6529b7577380bfa641dbfa 100644 (file)
@@ -488,7 +488,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
 }
 
 static void
-frame_clear(PyFrameObject *f)
+frame_tp_clear(PyFrameObject *f)
 {
     PyObject **fastlocals, **p, **oldtop;
     Py_ssize_t i, slots;
@@ -500,6 +500,7 @@ frame_clear(PyFrameObject *f)
      */
     oldtop = f->f_stacktop;
     f->f_stacktop = NULL;
+    f->f_executing = 0;
 
     Py_CLEAR(f->f_exc_type);
     Py_CLEAR(f->f_exc_value);
@@ -519,6 +520,25 @@ frame_clear(PyFrameObject *f)
     }
 }
 
+static PyObject *
+frame_clear(PyFrameObject *f)
+{
+    if (f->f_executing) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "cannot clear an executing frame");
+        return NULL;
+    }
+    if (f->f_gen) {
+        _PyGen_Finalize(f->f_gen);
+        assert(f->f_gen == NULL);
+    }
+    frame_tp_clear(f);
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(clear__doc__,
+"F.clear(): clear most references held by the frame");
+
 static PyObject *
 frame_sizeof(PyFrameObject *f)
 {
@@ -538,6 +558,8 @@ PyDoc_STRVAR(sizeof__doc__,
 "F.__sizeof__() -> size of F in memory, in bytes");
 
 static PyMethodDef frame_methods[] = {
+    {"clear",           (PyCFunction)frame_clear,       METH_NOARGS,
+     clear__doc__},
     {"__sizeof__",      (PyCFunction)frame_sizeof,      METH_NOARGS,
      sizeof__doc__},
     {NULL,              NULL}   /* sentinel */
@@ -566,7 +588,7 @@ PyTypeObject PyFrame_Type = {
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
     0,                                          /* tp_doc */
     (traverseproc)frame_traverse,               /* tp_traverse */
-    (inquiry)frame_clear,                       /* tp_clear */
+    (inquiry)frame_tp_clear,                    /* tp_clear */
     0,                                          /* tp_richcompare */
     0,                                          /* tp_weaklistoffset */
     0,                                          /* tp_iter */
@@ -708,6 +730,8 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
     f->f_lasti = -1;
     f->f_lineno = code->co_firstlineno;
     f->f_iblock = 0;
+    f->f_executing = 0;
+    f->f_gen = NULL;
 
     _PyObject_GC_TRACK(f);
     return f;
index dfd90aa4b1be9dc4a84c5f682f4b8fc68884ce1b..08d30bf4b7b7e71d0f382bd9dae393eb5490749d 100644 (file)
@@ -15,8 +15,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
     return 0;
 }
 
-static void
-gen_finalize(PyObject *self)
+void
+_PyGen_Finalize(PyObject *self)
 {
     PyGenObject *gen = (PyGenObject *)self;
     PyObject *res;
@@ -140,6 +140,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
         Py_XDECREF(t);
         Py_XDECREF(v);
         Py_XDECREF(tb);
+        gen->gi_frame->f_gen = NULL;
         gen->gi_frame = NULL;
         Py_DECREF(f);
     }
@@ -505,7 +506,7 @@ PyTypeObject PyGen_Type = {
     0,                                          /* tp_weaklist */
     0,                                          /* tp_del */
     0,                                          /* tp_version_tag */
-    gen_finalize,                               /* tp_finalize */
+    _PyGen_Finalize,                            /* tp_finalize */
 };
 
 PyObject *
@@ -517,6 +518,7 @@ PyGen_New(PyFrameObject *f)
         return NULL;
     }
     gen->gi_frame = f;
+    f->f_gen = (PyObject *) gen;
     Py_INCREF(f->f_code);
     gen->gi_code = (PyObject *)(f->f_code);
     gen->gi_running = 0;
index 837b7c16bf139e4a657aebe2f80c31012c5551ad..465876bc05166c178acb1cf9aeb3420259bdd540 100644 (file)
@@ -1182,6 +1182,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
     stack_pointer = f->f_stacktop;
     assert(stack_pointer != NULL);
     f->f_stacktop = NULL;       /* remains NULL unless yield suspends frame */
+    f->f_executing = 1;
 
     if (co->co_flags & CO_GENERATOR && !throwflag) {
         if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
@@ -3206,6 +3207,7 @@ fast_yield:
     /* pop frame */
 exit_eval_frame:
     Py_LeaveRecursiveCall();
+    f->f_executing = 0;
     tstate->frame = f->f_back;
 
     return retval;