Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 74b1fc5

Browse files
author
Anselm Kruis
committed
Merge branch 'slp-issue-192' into slp-issue-199
2 parents b8cddac + e80e194 commit 74b1fc5

File tree

11 files changed

+167
-181
lines changed

11 files changed

+167
-181
lines changed

Doc/library/stackless/tasklets.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,19 @@ The ``tasklet`` class
342342
# Implement unsafe logic here.
343343
t.set_ignore_nesting(old_value)
344344

345+
.. method:: tasklet.__del__()
346+
347+
.. versionadded:: 3.7
348+
349+
Finalize the tasklet. This is a :pep:`442` finalizer. If this tasklet is
350+
alive and has a non-trivial C-state attached, the finalizer repeatedly
351+
kills the tasklet for upmost 10 times until it is dead. Then, if this
352+
tasklet still has non-trivial C-state attached, the finalizer appends the
353+
tasklet to :data:`gc.garbage`. This is done, because releasing the C-state
354+
could cause undefined behavior.
355+
356+
You should not call this method from |PY|-code.
357+
345358
The following (read-only) attributes allow tasklet state to be checked:
346359

347360
.. attribute:: tasklet.alive

Include/objimpl.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,6 @@ PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t);
336336
PyAPI_FUNC(void) PyObject_GC_Track(void *);
337337
PyAPI_FUNC(void) PyObject_GC_UnTrack(void *);
338338
PyAPI_FUNC(void) PyObject_GC_Del(void *);
339-
/* STACKLESS addition */
340-
PyAPI_FUNC(void) PyObject_GC_Collectable(PyObject *, visitproc, void*, int);
341339

342340
#define PyObject_GC_New(type, typeobj) \
343341
( (type *) _PyObject_GC_New(typeobj) )

Modules/gcmodule.c

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -419,48 +419,9 @@ untrack_dicts(PyGC_Head *head)
419419
}
420420

421421
/* Return true if object has a pre-PEP 442 finalization method. */
422-
/* STACKLESS addition to support collection of tasklets */
423-
424-
/* A traversal callback for has_finalisers. It is a dummy, used to identify
425-
* this phase of finalizer discovery by its function pointer, and thus
426-
* enable the action of PyObject_GC_Collectable() below.
427-
*/
428-
static int
429-
visit_has_finalizer(PyObject *op, void *data)
430-
{
431-
assert(op != NULL);
432-
return 0;
433-
}
434-
435-
/* An object can call this function from its traversal function
436-
* to indicate whether it wishes to be collected or not
437-
* this is useful for objects that have state that determines
438-
* whether non-trivial actions need to be undertaken when
439-
* it is deleted.
440-
*/
441-
PyAPI_FUNC(void)
442-
PyObject_GC_Collectable(PyObject *op, visitproc proc, void *arg,
443-
int can_collect)
444-
{
445-
/* only do this during the move_finalizers phase */
446-
if (proc == &visit_has_finalizer)
447-
*(int*)arg = can_collect;
448-
}
449-
450422
static int
451423
has_legacy_finalizer(PyObject *op)
452424
{
453-
/* first, dynamic decision per object */
454-
traverseproc traverse;
455-
int collectable;
456-
traverse = Py_TYPE(op)->tp_traverse;
457-
collectable = -1;
458-
(void) traverse(op,
459-
(visitproc)visit_has_finalizer,
460-
(void *)&collectable);
461-
if (collectable >= 0)
462-
return collectable == 0;
463-
464425
return op->ob_type->tp_del != NULL;
465426
}
466427

Stackless/changelog.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ 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 finalizer can be called from Python-code: tasklet.__del__().
15+
Stackless calls the finalizer only once. The finalizer repeatedly kills the
16+
tasklet until it is dead but at most 10-times.
17+
If the finalizer can't kill the C-state it adds the tasklet to gc.garbage.
18+
This change unifies the behavior of de-allocation caused by ref-counting and
19+
de-allocation caused by GC. And it brings the behavior more in line with
20+
generators.
21+
1222
- https://github.com/stackless-dev/stackless/issues/199
1323
Enable stackless calls of coroutines wrapped in "asyncio._CTask", if
1424
soft-switching is enabled. Now asyncio runs coroutines stackless.

Stackless/core/stackless_impl.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,8 +727,6 @@ int slp_return_wrapper_hard(PyObject *retval);
727727
int slp_int_wrapper(PyObject *retval);
728728
int slp_current_wrapper(int(*func)(PyTaskletObject*),
729729
PyTaskletObject *task);
730-
int slp_resurrect_and_kill(PyObject *self,
731-
void(*killer)(PyObject *));
732730

733731
/* stackless pickling support */
734732
PyObject * slp_coro_wrapper_reduce(PyObject *o, PyTypeObject * wrapper_type);

Stackless/core/stackless_util.c

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

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

189128
/*
190129
* 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;

0 commit comments

Comments
 (0)