Skip to content

Commit 1444ab0

Browse files
Anselm KruisAnselm Kruis
authored andcommitted
Stackless issue python#192: use tp_finalize for complex finalisation code
Refactor the finalisation code of stackless.tasklet and stackless.channel. - For tasklets, use a PEP 442 finalizer; - for channels a simple tp_clear function is sufficient. Like every PEP 442 other finalizer, the new finalizer runs only once for each tasklet.
1 parent cb1c4ea commit 1444ab0

File tree

5 files changed

+52
-92
lines changed

5 files changed

+52
-92
lines changed

Stackless/changelog.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/192
13+
Use the PEP 442 mechanic to finalize a tasklet instead of a arcane logic in
14+
tp_dealloc. The new code calls the finalizer only once. The finalizer tries
15+
to kill the tasklet, as long as it has an unfinished C-state. This change
16+
brings the behavior more in line with generators.
17+
1218
- https://github.com/stackless-dev/stackless/issues/193
1319
Fix the macros PyTasklet_CheckExact(op) and PyChannel_CheckExact(op).
1420
Previously they contained a reference to a non existing variable.

Stackless/core/stackless_impl.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -737,8 +737,6 @@ int slp_return_wrapper_hard(PyObject *retval);
737737
int slp_int_wrapper(PyObject *retval);
738738
int slp_current_wrapper(int(*func)(PyTaskletObject*),
739739
PyTaskletObject *task);
740-
int slp_resurrect_and_kill(PyObject *self,
741-
void(*killer)(PyObject *));
742740

743741
/* stackless pickling support */
744742
int slp_async_gen_init_hooks(PyAsyncGenObject *o);

Stackless/core/stackless_util.c

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -122,67 +122,6 @@ slp_current_wrapper( int(*func)(PyTaskletObject*), PyTaskletObject *task )
122122
return ret;
123123
}
124124

125-
int
126-
slp_resurrect_and_kill(PyObject *self, void(*killer)(PyObject *))
127-
{
128-
/* modelled after typeobject.c's slot_tp_del */
129-
130-
PyObject *error_type, *error_value, *error_traceback;
131-
132-
/* this is different from typeobject.c's slot_tp_del: our callers already
133-
called PyObject_GC_UnTrack(self) */
134-
assert(PyObject_IS_GC(self) && ! _PyObject_GC_IS_TRACKED(self));
135-
136-
/* Temporarily resurrect the object. */
137-
assert(Py_REFCNT(self) == 0);
138-
Py_REFCNT(self) = 1;
139-
140-
/* Save the current exception, if any. */
141-
PyErr_Fetch(&error_type, &error_value, &error_traceback);
142-
143-
killer(self);
144-
145-
/* Restore the saved exception. */
146-
PyErr_Restore(error_type, error_value, error_traceback);
147-
148-
/* Undo the temporary resurrection; can't use DECREF here, it would
149-
* cause a recursive call.
150-
*/
151-
assert(Py_REFCNT(self) > 0);
152-
if (--Py_REFCNT(self) == 0)
153-
return 0; /* this is the normal path out */
154-
155-
/* __del__ resurrected it! Make it look like the original Py_DECREF
156-
* never happened.
157-
*/
158-
{
159-
Py_ssize_t refcnt = Py_REFCNT(self);
160-
_Py_NewReference(self);
161-
Py_REFCNT(self) = refcnt;
162-
}
163-
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
164-
* we need to undo that. */
165-
_Py_DEC_REFTOTAL;
166-
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
167-
* chain, so no more to do there.
168-
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
169-
* _Py_NewReference bumped tp_allocs: both of those need to be
170-
* undone.
171-
*/
172-
#ifdef COUNT_ALLOCS
173-
--Py_TYPE(self)->tp_frees;
174-
--Py_TYPE(self)->tp_allocs;
175-
#endif
176-
/* This code copied from iobase_dealloc() (in 3.0) */
177-
/* When called from a heap type's dealloc, the type will be
178-
decref'ed on return (see e.g. subtype_dealloc in typeobject.c).
179-
This will undo that, thus preventing a crash. But such a type
180-
_will_ have had its dict and slots cleared. */
181-
if (PyType_HasFeature(Py_TYPE(self), Py_TPFLAGS_HEAPTYPE))
182-
Py_INCREF(Py_TYPE(self));
183-
184-
return -1;
185-
}
186125

187126
/*
188127
* A thread id is either an unsigned long or the special value -1.

Stackless/module/channelobject.c

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
#include "core/stackless_impl.h"
1212
#include "channelobject.h"
1313

14-
static void
15-
channel_remove_all(PyObject *ob);
16-
1714
Py_LOCAL_INLINE(PyChannelFlagStruc)
1815
channel_flags_from_integer(int flags) {
1916
#if defined(SLP_USE_NATIVE_BITFIELD_LAYOUT) && SLP_USE_NATIVE_BITFIELD_LAYOUT
@@ -62,23 +59,19 @@ channel_traverse(PyChannelObject *ch, visitproc visit, void *arg)
6259

6360
static void
6461
channel_clear(PyObject *ob)
65-
{
66-
/* this function does nothing but decref, so it's safe to use */
67-
channel_remove_all(ob);
68-
}
69-
70-
static void
71-
channel_remove_all(PyObject *ob)
7262
{
7363
PyChannelObject *ch = (PyChannelObject *) ob;
64+
assert(PyChannel_Check(ob));
7465

7566
/*
7667
* remove all tasklets and hope they will die.
7768
* Note that the channel might receive new actions
7869
* during tasklet deallocation, so we don't even know
7970
* if it will have the same direction. :-/
8071
*/
72+
ch->flags.closing = 1;
8173
while (ch->balance) {
74+
/* this function does nothing but setting task->flags.blocked = 0, so it's safe to use */
8275
ob = (PyObject *) slp_channel_remove(ch, NULL, NULL, NULL);
8376
Py_DECREF(ob);
8477
}
@@ -88,15 +81,10 @@ static void
8881
channel_dealloc(PyObject *ob)
8982
{
9083
PyChannelObject *ch = (PyChannelObject *) ob;
91-
PyObject_GC_UnTrack(ob);
9284

93-
if (ch->balance) {
94-
if (slp_resurrect_and_kill(ob, channel_remove_all)) {
95-
/* the beast has grown new references */
96-
PyObject_GC_Track(ob);
97-
return;
98-
}
99-
}
85+
86+
PyObject_GC_UnTrack(ob);
87+
channel_clear(ob);
10088
if (ch->chan_weakreflist != NULL)
10189
PyObject_ClearWeakRefs((PyObject *)ch);
10290
Py_TYPE(ob)->tp_free(ob);
@@ -1152,7 +1140,7 @@ channel_setstate(PyObject *self, PyObject *args)
11521140
&PyList_Type, &lis))
11531141
return NULL;
11541142

1155-
channel_remove_all((PyObject *) ch);
1143+
channel_clear((PyObject *) ch);
11561144
n = PyList_GET_SIZE(lis);
11571145
ch->flags = channel_flags_from_integer(flags);
11581146
dir = balance > 0 ? 1 : -1;

Stackless/module/taskletobject.c

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,24 +249,43 @@ exc_state_clear(_PyErr_StackItem *exc_state)
249249
}
250250

251251
static void
252-
tasklet_dealloc(PyTaskletObject *t)
252+
tasklet_finalize(PyObject *self)
253253
{
254-
PyObject_GC_UnTrack(t);
254+
PyTaskletObject *t;
255+
PyObject *error_type, *error_value, *error_traceback;
256+
257+
assert(PyTasklet_Check(self));
258+
t = (PyTaskletObject *)self;
259+
260+
/* Save the current exception, if any. */
261+
PyErr_Fetch(&error_type, &error_value, &error_traceback);
262+
255263
if (tasklet_has_c_stack_and_thread(t)) {
256264
/*
257265
* we want to cleanly kill the tasklet in the case it
258-
* was forgotten. One way would be to resurrect it,
259-
* but this is quite ugly with many ifdefs, see
260-
* classobject/typeobject.
261-
* Well, we do it.
266+
* was forgotten.
267+
*/
268+
kill_finally(self);
269+
}
270+
271+
/* Restore the saved exception. */
272+
PyErr_Restore(error_type, error_value, error_traceback);
273+
}
274+
275+
static void
276+
tasklet_dealloc(PyTaskletObject *t)
277+
{
278+
if (PyTasklet_CheckExact(t)) {
279+
/* When ob is subclass of stackless.tasklet, finalizer is called from
280+
* subtype_dealloc.
262281
*/
263-
if (slp_resurrect_and_kill((PyObject *) t, kill_finally)) {
264-
/* the beast has grown new references */
265-
PyObject_GC_Track(t);
282+
if (PyObject_CallFinalizerFromDealloc((PyObject *)t) < 0) {
283+
// resurrected.
266284
return;
267285
}
268286
}
269287

288+
PyObject_GC_UnTrack(t);
270289
tasklet_clear_frames(t);
271290
if (t->tsk_weakreflist != NULL)
272291
PyObject_ClearWeakRefs((PyObject *)t);
@@ -2339,7 +2358,8 @@ PyTypeObject PyTasklet_Type = {
23392358
PyObject_GenericSetAttr, /* tp_setattro */
23402359
0, /* tp_as_buffer */
23412360
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
2342-
Py_TPFLAGS_BASETYPE, /* tp_flags */
2361+
Py_TPFLAGS_BASETYPE |
2362+
Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */
23432363
tasklet__doc__, /* tp_doc */
23442364
(traverseproc)tasklet_traverse, /* tp_traverse */
23452365
(inquiry) tasklet_clear, /* tp_clear */
@@ -2359,5 +2379,14 @@ PyTypeObject PyTasklet_Type = {
23592379
0, /* tp_alloc */
23602380
tasklet_new, /* tp_new */
23612381
PyObject_GC_Del, /* tp_free */
2382+
0, /* tp_is_gc */
2383+
0, /* tp_bases */
2384+
0, /* tp_mro */
2385+
0, /* tp_cache */
2386+
0, /* tp_subclasses */
2387+
0, /* tp_weaklist */
2388+
0, /* tp_del */
2389+
0, /* tp_version_tag */
2390+
tasklet_finalize, /* tp_finalize */
23622391
};
23632392
#endif

0 commit comments

Comments
 (0)