Hi Stefan, sorry for the delay, I was in the US for parental leave. I had hoped to find some time to work on the promised patch over there, but I fell short.
On 04/22/2013 02:28 PM, Stefan Behnel wrote: > Please do. Just ask back on this list if there's anything that's not > clear to you. I attached my current (trivial) patch. Currently I only support a decorator @cython.noclear cdef class ... to inhibit generation of tp_clear. Before I continue with this approach I am wondering about the API. Is noclear usable as a name? I think not because nobody will know what it is talking about. But I do not know how to press the information "do not generate the tp_clear slot which will clear references to break reference cycles during GC" into a short name. Perhaps something along the lines of "@cython.gc_keep_references" or "@cython.exempt_from_gc_cycle_breaker"!? How should the decorator to completely disable GC for a class be called? @cython.nogc? @cython.refcounted (because the class will only support cleanup via reference counting)? Any input appreciated. Greetings, Torsten -- DYNAmore Gesellschaft fuer Ingenieurdienstleistungen mbH Torsten Landschoff Office Dresden Tel: +49-(0)351-4519587 Fax: +49-(0)351-4519561 mailto:torsten.landsch...@dynamore.de http://www.dynamore.de DYNAmore Gesellschaft für FEM Ingenieurdienstleistungen mbH Registration court: Stuttgart, HRB 733694 Managing director: Prof. Dr. Karl Schweizerhof, Dipl.-Math. Ulrich Franz
>From 48cc65e658ac8f831661c5a880cf43154c919011 Mon Sep 17 00:00:00 2001 From: Torsten Landschoff <torsten.landsch...@dynamore.de> Date: Wed, 10 Jul 2013 23:58:08 +0200 Subject: [PATCH] Inhibit tp_clear generation via class decorator. --- Cython/Compiler/ModuleNode.py | 3 +- Cython/Compiler/Options.py | 3 ++ Cython/Compiler/TypeSlots.py | 9 ++++- tests/run/noclear.h | 64 ++++++++++++++++++++++++++++++++ tests/run/noclear.pyx | 82 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 tests/run/noclear.h create mode 100644 tests/run/noclear.pyx diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index ce6c304..791fc8e 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -1009,7 +1009,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): self.generate_dealloc_function(scope, code) if scope.needs_gc(): self.generate_traverse_function(scope, code, entry) - self.generate_clear_function(scope, code, entry) + if not scope.directives.get('noclear', False): + self.generate_clear_function(scope, code, entry) if scope.defines_any(["__getitem__"]): self.generate_getitem_int_function(scope, code) if scope.defines_any(["__setitem__", "__delitem__"]): diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index 795ae86..f09e3e3 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -96,6 +96,7 @@ directive_defaults = { 'final' : False, 'internal' : False, 'profile': False, + 'noclear': False, 'linetrace': False, 'infer_types': None, 'infer_types.verbose': False, @@ -201,6 +202,7 @@ directive_types = { 'ccall' : None, 'cclass' : None, 'returns' : type, + 'noclear': bool, 'set_initial_path': str, 'freelist': int, 'c_string_type': one_of('bytes', 'str', 'unicode'), @@ -214,6 +216,7 @@ for key, val in directive_defaults.items(): directive_scopes = { # defaults to available everywhere # 'module', 'function', 'class', 'with statement' 'final' : ('cclass', 'function'), + 'noclear' : ('cclass',), 'internal' : ('cclass',), 'autotestdict' : ('module',), 'autotestdict.all' : ('module',), diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py index 0413e4a..7bc9fb8 100644 --- a/Cython/Compiler/TypeSlots.py +++ b/Cython/Compiler/TypeSlots.py @@ -331,6 +331,13 @@ class GCDependentSlot(InternalMethodSlot): return InternalMethodSlot.slot_code(self, scope) +class GCClearReferencesSlot(GCDependentSlot): + def slot_code(self, scope): + if scope.directives.get('noclear', False): + return "0" + return GCDependentSlot.slot_code(self, scope) + + class ConstructorSlot(InternalMethodSlot): # Descriptor for tp_new and tp_dealloc. @@ -753,7 +760,7 @@ slot_table = ( DocStringSlot("tp_doc"), GCDependentSlot("tp_traverse"), - GCDependentSlot("tp_clear"), + GCClearReferencesSlot("tp_clear"), # Later -- synthesize a method to split into separate ops? MethodSlot(richcmpfunc, "tp_richcompare", "__richcmp__", inherited=False), # Py3 checks for __hash__ diff --git a/tests/run/noclear.h b/tests/run/noclear.h new file mode 100644 index 0000000..144762c --- /dev/null +++ b/tests/run/noclear.h @@ -0,0 +1,64 @@ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +typedef struct { + int magic; + int usage_counter; +} fakep11_context; + +typedef struct { + char *name; +} fakep11_slot; + +fakep11_context *fakep11_ctx_new(void); +fakep11_slot *fakep11_enumerate(fakep11_context *ctx, unsigned int *nslots); +void fakep11_release_all(fakep11_context *ctx, fakep11_slot *slots, unsigned int nslots); +void fakep11_ctx_free(fakep11_context *ctx); + +fakep11_context *fakep11_ctx_new(void) +{ + fakep11_context *p = (fakep11_context *) calloc(1, sizeof(*p)); + p->magic = 0x37e6712f; + return p; +} + +fakep11_slot *fakep11_enumerate(fakep11_context *ctx, unsigned int *nslots) +{ + int NSLOTS = 13; + int i; + char buf[128]; + fakep11_slot *slots = (fakep11_slot *) calloc(NSLOTS, sizeof(*slots)); + + for (i = 0; i < NSLOTS; i++) { + sprintf(buf, "Slot #%d", i); + slots[i].name = strdup(buf); + } + ++ctx->usage_counter; + *nslots = NSLOTS; + return slots; +} + +void fakep11_release_all(fakep11_context *ctx, fakep11_slot *slots, unsigned int nslots) +{ + /* This simulates the crash that is happening in the real p11wrap. I expected that + this function will not even be called due to a null pointer dereference via the + cleared Slots.context attribute but in fact Slots.context becomes None in tp_clear + so that the imp pointer can still be read. */ + if (ctx->magic != 0x37e6712f) { + fprintf(stderr, "Bad magic number %x, aborting.\n", ctx->magic); + exit(1); + } + free(slots); + --ctx->usage_counter; +} + +void fakep11_ctx_free(fakep11_context *ctx) +{ + if (ctx->magic != 0x37e6712f) { + fprintf(stderr, "Bad magic number %x, aborting.\n", ctx->magic); + exit(1); + } + ctx->magic = 0xdeadbeaf; + free(ctx); +} diff --git a/tests/run/noclear.pyx b/tests/run/noclear.pyx new file mode 100644 index 0000000..b353288 --- /dev/null +++ b/tests/run/noclear.pyx @@ -0,0 +1,82 @@ +cimport cython + + +cdef extern from "noclear.h": + ctypedef struct fakep11_context: + int usage_counter + + ctypedef struct fakep11_slot: + char *name + + fakep11_context *fakep11_ctx_new() + fakep11_slot *fakep11_enumerate(fakep11_context *ctx, unsigned int *nslots) + void fakep11_release_all(fakep11_context *ctx, fakep11_slot *slots, unsigned int nslots) + void fakep11_ctx_free(fakep11_context *ctx) + + +cdef class Context: + cdef fakep11_context *imp + + def __cinit__(self): + self.imp = fakep11_ctx_new() + + def __dealloc__(self): + fakep11_ctx_free(self.imp) + self.imp = NULL + + def slots(self): + return Slots(self).as_list() + + property usage_counter: + def __get__(self): + return self.imp.usage_counter + + +@cython.internal +@cython.noclear +cdef class Slots: + + cdef Context context + cdef unsigned int nslots + cdef fakep11_slot *slots + + def __cinit__(self, Context context): + self.context = context + self.slots = fakep11_enumerate(context.imp, &self.nslots) + + def __dealloc__(self): + print "Slots dealloc" + fakep11_release_all(self.context.imp, self.slots, self.nslots) + + def as_list(self): + l = [None] * self.nslots + for x in range(self.nslots): + l[x] = Slot().bind(self, &self.slots[x]) + return l + +cdef class Slot: + cdef Slots slots + cdef fakep11_slot *imp + + def __cinit__(self): + self.slots = None + self.imp = NULL + + cdef Slot bind(self, Slots slots, fakep11_slot *imp): + self.slots = slots + self.imp = imp + return self + + property name: + def __get__(self): + return self.imp.name + + +def crashes(): + """ + >>> ctx = Context() + >>> slots = ctx.slots() + >>> a = {"slot": slots[0]} + >>> b = {"slot": slots[1], "cycle": a} + >>> a["cycle"] = b + """ -- 1.7.10.362.g010b2
_______________________________________________ cython-devel mailing list cython-devel@python.org http://mail.python.org/mailman/listinfo/cython-devel