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

Reply via email to