summaryrefslogtreecommitdiffstats
path: root/Objects/genobject.c
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2013-05-14 18:37:52 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2013-05-14 18:37:52 (GMT)
commit93963569487bd3d13962c92d64a128e27330cf02 (patch)
tree6966ad6d6148e5ede6183a80a62890470f10d612 /Objects/genobject.c
parentb710d7e4c320fb299cf1ab2ec12e3821dc338bc8 (diff)
downloadcpython-93963569487bd3d13962c92d64a128e27330cf02.zip
cpython-93963569487bd3d13962c92d64a128e27330cf02.tar.gz
cpython-93963569487bd3d13962c92d64a128e27330cf02.tar.bz2
Backout c89febab4648 following private feedback by Guido.
(Issue #17807: Generators can now be finalized even when they are part of a reference cycle)
Diffstat (limited to 'Objects/genobject.c')
-rw-r--r--Objects/genobject.c251
1 files changed, 219 insertions, 32 deletions
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 34ecf2c..016bfa2 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -19,50 +19,112 @@ static void
gen_dealloc(PyGenObject *gen)
{
PyObject *self = (PyObject *) gen;
- PyFrameObject *f = gen->gi_frame;
_PyObject_GC_UNTRACK(gen);
if (gen->gi_weakreflist != NULL)
PyObject_ClearWeakRefs(self);
- gen->gi_frame = NULL;
- if (f) {
- /* Close the generator by finalizing the frame */
- PyObject *retval, *t, *v, *tb;
- PyErr_Fetch(&t, &v, &tb);
- f->f_gen = NULL;
- retval = _PyFrame_Finalize(f);
- if (retval)
- Py_DECREF(retval);
- else if (PyErr_Occurred())
- PyErr_WriteUnraisable((PyObject *) gen);
- Py_DECREF(f);
- PyErr_Restore(t, v, tb);
+ _PyObject_GC_TRACK(self);
+
+ if (gen->gi_frame != NULL && gen->gi_frame->f_stacktop != NULL) {
+ /* Generator is paused, so we need to close */
+ Py_TYPE(gen)->tp_del(self);
+ if (self->ob_refcnt > 0)
+ return; /* resurrected. :( */
}
+
+ _PyObject_GC_UNTRACK(self);
+ Py_CLEAR(gen->gi_frame);
Py_CLEAR(gen->gi_code);
PyObject_GC_Del(gen);
}
+
static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
+ PyThreadState *tstate = PyThreadState_GET();
PyFrameObject *f = gen->gi_frame;
+ PyObject *result;
- /* For compatibility, we check gi_running before f == NULL */
if (gen->gi_running) {
PyErr_SetString(PyExc_ValueError,
"generator already executing");
return NULL;
}
- if (f == NULL) {
- /* Only set exception if send() called, not throw() or next() */
+ if (f == NULL || f->f_stacktop == NULL) {
+ /* Only set exception if called from send() */
if (arg && !exc)
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
- return _PyFrame_GeneratorSend(f, arg, exc);
+ if (f->f_lasti == -1) {
+ if (arg && arg != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "can't send non-None value to a "
+ "just-started generator");
+ return NULL;
+ }
+ } else {
+ /* Push arg onto the frame's value stack */
+ result = arg ? arg : Py_None;
+ Py_INCREF(result);
+ *(f->f_stacktop++) = result;
+ }
+
+ /* Generators always return to their most recent caller, not
+ * necessarily their creator. */
+ Py_XINCREF(tstate->frame);
+ assert(f->f_back == NULL);
+ f->f_back = tstate->frame;
+
+ gen->gi_running = 1;
+ result = PyEval_EvalFrameEx(f, exc);
+ gen->gi_running = 0;
+
+ /* Don't keep the reference to f_back any longer than necessary. It
+ * may keep a chain of frames alive or it could create a reference
+ * cycle. */
+ assert(f->f_back == tstate->frame);
+ Py_CLEAR(f->f_back);
+
+ /* If the generator just returned (as opposed to yielding), signal
+ * that the generator is exhausted. */
+ if (result && f->f_stacktop == NULL) {
+ if (result == Py_None) {
+ /* Delay exception instantiation if we can */
+ PyErr_SetNone(PyExc_StopIteration);
+ } else {
+ PyObject *e = PyObject_CallFunctionObjArgs(
+ PyExc_StopIteration, result, NULL);
+ if (e != NULL) {
+ PyErr_SetObject(PyExc_StopIteration, e);
+ Py_DECREF(e);
+ }
+ }
+ Py_CLEAR(result);
+ }
+
+ if (!result || f->f_stacktop == NULL) {
+ /* generator can't be rerun, so release the frame */
+ /* first clean reference cycle through stored exception traceback */
+ PyObject *t, *v, *tb;
+ t = f->f_exc_type;
+ v = f->f_exc_value;
+ tb = f->f_exc_traceback;
+ f->f_exc_type = NULL;
+ f->f_exc_value = NULL;
+ f->f_exc_traceback = NULL;
+ Py_XDECREF(t);
+ Py_XDECREF(v);
+ Py_XDECREF(tb);
+ gen->gi_frame = NULL;
+ Py_DECREF(f);
+ }
+
+ return result;
}
PyDoc_STRVAR(send_doc,
@@ -83,33 +145,146 @@ PyDoc_STRVAR(close_doc,
* close a subiterator being delegated to by yield-from.
*/
+static int
+gen_close_iter(PyObject *yf)
+{
+ PyObject *retval = NULL;
+ _Py_IDENTIFIER(close);
+
+ if (PyGen_CheckExact(yf)) {
+ retval = gen_close((PyGenObject *)yf, NULL);
+ if (retval == NULL)
+ return -1;
+ } else {
+ PyObject *meth = _PyObject_GetAttrId(yf, &PyId_close);
+ if (meth == NULL) {
+ if (!PyErr_ExceptionMatches(PyExc_AttributeError))
+ PyErr_WriteUnraisable(yf);
+ PyErr_Clear();
+ } else {
+ retval = PyObject_CallFunction(meth, "");
+ Py_DECREF(meth);
+ if (retval == NULL)
+ return -1;
+ }
+ }
+ Py_XDECREF(retval);
+ return 0;
+}
+
static PyObject *
gen_yf(PyGenObject *gen)
{
+ PyObject *yf = NULL;
PyFrameObject *f = gen->gi_frame;
- if (f)
- return _PyFrame_YieldingFrom(f);
- else
- return NULL;
+
+ if (f && f->f_stacktop) {
+ PyObject *bytecode = f->f_code->co_code;
+ unsigned char *code = (unsigned char *)PyBytes_AS_STRING(bytecode);
+
+ if (code[f->f_lasti + 1] != YIELD_FROM)
+ return NULL;
+ yf = f->f_stacktop[-1];
+ Py_INCREF(yf);
+ }
+
+ return yf;
}
static PyObject *
gen_close(PyGenObject *gen, PyObject *args)
{
- PyFrameObject *f = gen->gi_frame;
+ PyObject *retval;
+ PyObject *yf = gen_yf(gen);
+ int err = 0;
- /* For compatibility, we check gi_running before f == NULL */
- if (gen->gi_running) {
- PyErr_SetString(PyExc_ValueError,
- "generator already executing");
+ if (yf) {
+ gen->gi_running = 1;
+ err = gen_close_iter(yf);
+ gen->gi_running = 0;
+ Py_DECREF(yf);
+ }
+ if (err == 0)
+ PyErr_SetNone(PyExc_GeneratorExit);
+ retval = gen_send_ex(gen, Py_None, 1);
+ if (retval) {
+ Py_DECREF(retval);
+ PyErr_SetString(PyExc_RuntimeError,
+ "generator ignored GeneratorExit");
return NULL;
}
- if (f == NULL)
- Py_RETURN_NONE;
+ if (PyErr_ExceptionMatches(PyExc_StopIteration)
+ || PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
+ PyErr_Clear(); /* ignore these errors */
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return NULL;
+}
- return _PyFrame_Finalize(f);
+static void
+gen_del(PyObject *self)
+{
+ PyObject *res;
+ PyObject *error_type, *error_value, *error_traceback;
+ PyGenObject *gen = (PyGenObject *)self;
+
+ if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
+ /* Generator isn't paused, so no need to close */
+ return;
+
+ /* Temporarily resurrect the object. */
+ assert(self->ob_refcnt == 0);
+ self->ob_refcnt = 1;
+
+ /* Save the current exception, if any. */
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+
+ res = gen_close(gen, NULL);
+
+ if (res == NULL)
+ PyErr_WriteUnraisable(self);
+ else
+ Py_DECREF(res);
+
+ /* Restore the saved exception. */
+ PyErr_Restore(error_type, error_value, error_traceback);
+
+ /* Undo the temporary resurrection; can't use DECREF here, it would
+ * cause a recursive call.
+ */
+ assert(self->ob_refcnt > 0);
+ if (--self->ob_refcnt == 0)
+ return; /* this is the normal path out */
+
+ /* close() resurrected it! Make it look like the original Py_DECREF
+ * never happened.
+ */
+ {
+ Py_ssize_t refcnt = self->ob_refcnt;
+ _Py_NewReference(self);
+ self->ob_refcnt = refcnt;
+ }
+ assert(PyType_IS_GC(Py_TYPE(self)) &&
+ _Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
+
+ /* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
+ * we need to undo that. */
+ _Py_DEC_REFTOTAL;
+ /* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
+ * chain, so no more to do there.
+ * If COUNT_ALLOCS, the original decref bumped tp_frees, and
+ * _Py_NewReference bumped tp_allocs: both of those need to be
+ * undone.
+ */
+#ifdef COUNT_ALLOCS
+ --(Py_TYPE(self)->tp_frees);
+ --(Py_TYPE(self)->tp_allocs);
+#endif
}
+
+
PyDoc_STRVAR(throw_doc,
"throw(typ[,val[,tb]]) -> raise exception in generator,\n\
return next yielded value or raise StopIteration.");
@@ -131,7 +306,7 @@ gen_throw(PyGenObject *gen, PyObject *args)
int err;
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit)) {
gen->gi_running = 1;
- err = _PyFrame_CloseIterator(yf);
+ err = gen_close_iter(yf);
gen->gi_running = 0;
Py_DECREF(yf);
if (err < 0)
@@ -369,6 +544,7 @@ PyTypeObject PyGen_Type = {
0, /* tp_cache */
0, /* tp_subclasses */
0, /* tp_weaklist */
+ gen_del, /* tp_del */
};
PyObject *
@@ -380,7 +556,6 @@ 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;
@@ -392,5 +567,17 @@ PyGen_New(PyFrameObject *f)
int
PyGen_NeedsFinalizing(PyGenObject *gen)
{
+ int i;
+ PyFrameObject *f = gen->gi_frame;
+
+ if (f == NULL || f->f_stacktop == NULL)
+ return 0; /* no frame or empty blockstack == no finalization */
+
+ /* Any block type besides a loop requires cleanup. */
+ for (i = 0; i < f->f_iblock; i++)
+ if (f->f_blockstack[i].b_type != SETUP_LOOP)
+ return 1;
+
+ /* No blocks except loops, it's safe to skip finalization. */
return 0;
}