[pypy-commit] pypy reverse-debugger: Finalizers: recording

arigo pypy.commits at gmail.com
Mon Jun 27 08:59:02 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: reverse-debugger
Changeset: r85397:0477d9c6b062
Date: 2016-06-27 15:00 +0200
http://bitbucket.org/pypy/pypy/changeset/0477d9c6b062/

Log:	Finalizers: recording

diff --git a/rpython/translator/c/src/mem.c b/rpython/translator/c/src/mem.c
--- a/rpython/translator/c/src/mem.c
+++ b/rpython/translator/c/src/mem.c
@@ -80,6 +80,9 @@
 RPY_EXTERN void (*boehm_fq_trigger[])(void);
 
 int boehm_gc_finalizer_lock = 0;
+void boehm_gc_finalizer_notifier(void);
+
+#ifndef RPY_REVERSE_DEBUGGER
 void boehm_gc_finalizer_notifier(void)
 {
     int i;
@@ -101,6 +104,10 @@
 
     boehm_gc_finalizer_lock--;
 }
+#else
+/* see revdb.c */
+RPY_EXTERN void rpy_reverse_db_next_dead(void *);
+#endif
 
 static void mem_boehm_ignore(char *msg, GC_word arg)
 {
@@ -128,12 +135,17 @@
 void *boehm_fq_next_dead(struct boehm_fq_s **fqueue)
 {
     struct boehm_fq_s *node = *fqueue;
+    void *result;
     if (node != NULL) {
         *fqueue = node->next;
-        return node->obj;
+        result = node->obj;
     }
     else
-        return NULL;
+        result = NULL;
+#ifdef RPY_REVERSE_DEBUGGER
+    rpy_reverse_db_next_dead(result);
+#endif
+    return result;
 }
 #endif /* BOEHM GC */
 
diff --git a/rpython/translator/revdb/src-revdb/revdb.c b/rpython/translator/revdb/src-revdb/revdb.c
--- a/rpython/translator/revdb/src-revdb/revdb.c
+++ b/rpython/translator/revdb/src-revdb/revdb.c
@@ -25,7 +25,7 @@
 #define WEAKREF_AFTERWARDS_DEAD    ((char)0xf2)
 #define WEAKREF_AFTERWARDS_ALIVE   ((char)0xeb)
 
-//#define ASYNC_FINALIZER       ((int16_t)0xff46)
+#define ASYNC_FINALIZER_TRIGGER    ((int16_t)0xff46)
 
 
 typedef struct {
@@ -82,6 +82,18 @@
         check_at_end(stop_points);
 }
 
+static void record_stop_point(void);
+static void replay_stop_point(void);
+
+RPY_EXTERN
+void rpy_reverse_db_stop_point(void)
+{
+    if (!RPY_RDB_REPLAY)
+        record_stop_point();
+    else
+        replay_stop_point();
+}
+
 
 /* ------------------------------------------------------------ */
 /* Recording mode                                               */
@@ -145,23 +157,81 @@
 static void flush_buffer(void)
 {
     /* write the current buffer content to the OS */
-    ssize_t size = rpy_revdb.buf_p - rpy_rev_buffer;
+    ssize_t full_size = rpy_revdb.buf_p - rpy_rev_buffer;
     rpy_revdb.buf_p = rpy_rev_buffer + sizeof(int16_t);
     if (rpy_rev_fileno >= 0)
-        write_all(rpy_rev_buffer, size);
+        write_all(rpy_rev_buffer, full_size);
+}
+
+static ssize_t current_packet_size(void)
+{
+    return rpy_revdb.buf_p - (rpy_rev_buffer + sizeof(int16_t));
 }
 
 RPY_EXTERN
 void rpy_reverse_db_flush(void)
 {
-    ssize_t packet_size = rpy_revdb.buf_p - (rpy_rev_buffer + sizeof(int16_t));
-    if (packet_size != 0) {
-        assert(0 < packet_size && packet_size <= 32767);
-        *(int16_t *)rpy_rev_buffer = packet_size;
+    ssize_t content_size = current_packet_size();
+    if (content_size != 0) {
+        assert(0 < content_size && content_size <= 32767);
+        *(int16_t *)rpy_rev_buffer = content_size;
         flush_buffer();
     }
 }
 
+/*
+RPY_EXTERN
+void rpy_reverse_db_register_finalizer(void *obj, void (*finalizer)(void *))
+{
+    ...;
+}
+*/
+
+void boehm_gc_finalizer_notifier(void)
+{
+    /* This is called by Boehm when there are pending finalizers.
+       They are only invoked when we call GC_invoke_finalizers(),
+       which we only do at stop points in the case of revdb. 
+    */
+    assert(rpy_revdb.stop_point_break <= rpy_revdb.stop_point_seen + 1);
+    rpy_revdb.stop_point_break = rpy_revdb.stop_point_seen + 1;
+}
+
+static void record_stop_point(void)
+{
+    /* Invoke the finalizers now.  This will call boehm_fq_callback(),
+       which will enqueue the objects in the correct FinalizerQueue.
+       Then, call boehm_fq_trigger(), which calls finalizer_trigger().
+    */
+    int i;
+    rpy_reverse_db_flush();
+
+    GC_invoke_finalizers();
+    i = 0;
+    while (boehm_fq_trigger[i])
+        boehm_fq_trigger[i++]();
+
+    /* This should all be done without emitting anything to the rdb
+       log.  We check that, and emit just a ASYNC_FINALIZER_TRIGGER.
+    */
+    if (current_packet_size() != 0) {
+        fprintf(stderr,
+                "record_stop_point emitted unexpectedly to the rdb log\n");
+        exit(1);
+    }
+    *(int16_t *)rpy_rev_buffer = ASYNC_FINALIZER_TRIGGER;
+    memcpy(rpy_revdb.buf_p, &rpy_revdb.stop_point_seen, sizeof(uint64_t));
+    rpy_revdb.buf_p += sizeof(uint64_t);
+    flush_buffer();
+}
+
+RPY_EXTERN
+void rpy_reverse_db_next_dead(void *result)
+{
+    int64_t uid = result ? ((struct pypy_header0 *)result)->h_uid : -1;
+    RPY_REVDB_EMIT(/* nothing */, int64_t _e, uid);
+}
+
 RPY_EXTERN
 Signed rpy_reverse_db_identityhash(struct pypy_header0 *obj)
 {
@@ -828,8 +898,7 @@
     rpy_revdb.watch_enabled = any_watch_point;
 }
 
-RPY_EXTERN
-void rpy_reverse_db_stop_point(void)
+static void replay_stop_point(void)
 {
     while (rpy_revdb.stop_point_break == rpy_revdb.stop_point_seen) {
         save_state();
diff --git a/rpython/translator/revdb/src-revdb/revdb_include.h b/rpython/translator/revdb/src-revdb/revdb_include.h
--- a/rpython/translator/revdb/src-revdb/revdb_include.h
+++ b/rpython/translator/revdb/src-revdb/revdb_include.h
@@ -112,6 +112,9 @@
 #define OP_REVDB_WEAKREF_DEREF(weakref, r)                              \
     r = rpy_reverse_db_weakref_deref(weakref)
 
+#define OP_REVDB_CALL_DESTRUCTOR(obj, r)                                \
+    rpy_reverse_db_call_destructor(obj)
+
 RPY_EXTERN void rpy_reverse_db_flush(void);
 RPY_EXTERN char *rpy_reverse_db_fetch(const char *file, int line);
 RPY_EXTERN void rpy_reverse_db_stop_point(void);
@@ -125,5 +128,7 @@
 RPY_EXTERN void rpy_reverse_db_watch_restore_state(bool_t any_watch_point);
 RPY_EXTERN void *rpy_reverse_db_weakref_create(void *target);
 RPY_EXTERN void *rpy_reverse_db_weakref_deref(void *weakref);
+//RPY_EXTERN void rpy_reverse_db_call_destructor(void *obj);
+RPY_EXTERN void rpy_reverse_db_next_dead(void *result);
 
 /* ------------------------------------------------------------ */
diff --git a/rpython/translator/revdb/test/test_basic.py b/rpython/translator/revdb/test/test_basic.py
--- a/rpython/translator/revdb/test/test_basic.py
+++ b/rpython/translator/revdb/test/test_basic.py
@@ -44,6 +44,21 @@
         assert self.cur <= self.current_packet_end
         return result
 
+    def is_special_packet(self):
+        if self.current_packet_end != self.cur:
+            assert self.current_packet_end > self.cur
+            return False
+        next_header = struct.unpack_from('h', self.buffer, self.cur)[0]
+        return (next_header & 0xFF00) == 0xFF00
+
+    def special_packet(self, expected, fmt):
+        assert self.current_packet_end == self.cur
+        next_id = self.read1('h')
+        assert next_id == expected
+        p = self.cur
+        self.cur = self.current_packet_end = p + struct.calcsize(fmt)
+        return struct.unpack_from(fmt, self.buffer, p)
+
     def read_check_argv(self, expected):
         assert self.argc == len(expected)
         for i in range(self.argc):
diff --git a/rpython/translator/revdb/test/test_weak.py b/rpython/translator/revdb/test/test_weak.py
--- a/rpython/translator/revdb/test/test_weak.py
+++ b/rpython/translator/revdb/test/test_weak.py
@@ -2,6 +2,7 @@
 from rpython.rlib import revdb, rgc
 from rpython.rlib.debug import debug_print
 from rpython.rlib.objectmodel import keepalive_until_here
+from rpython.rlib.rarithmetic import intmask
 from rpython.translator.revdb.message import *
 from rpython.translator.revdb.test.test_basic import BaseRecordingTests
 from rpython.translator.revdb.test.test_basic import InteractiveTests
@@ -22,6 +23,8 @@
 WEAKREF_AFTERWARDS_DEAD  = chr(0xf2)
 WEAKREF_AFTERWARDS_ALIVE = chr(0xeb)
 
+ASYNC_FINALIZER_TRIGGER  = 0xff46 - 2**16
+
 
 class TestRecording(BaseRecordingTests):
 
@@ -80,6 +83,8 @@
             lst = [X() for i in range(3000)]
             for i in range(3000):
                 lst[i] = None
+                if i % 300 == 150:
+                    rgc.collect()
                 revdb.stop_point()
             return 9
         self.compile(main, [], backendopt=False)
@@ -88,6 +93,124 @@
         x = rdb.next('q'); assert x == 3000    # number of stop points
         assert rdb.done()
 
+    def test_finalizer_queue(self):
+        from rpython.rtyper.lltypesystem import lltype, rffi
+        from rpython.translator.tool.cbuild import ExternalCompilationInfo
+        eci = ExternalCompilationInfo(
+            pre_include_bits=["#define foobar(x) x\n"])
+        foobar = rffi.llexternal('foobar', [lltype.Signed], lltype.Signed,
+                                 compilation_info=eci)
+        class Glob:
+            pass
+        glob = Glob()
+        class X:
+            pass
+        class MyFinalizerQueue(rgc.FinalizerQueue):
+            Class = X
+            def finalizer_trigger(self):
+                glob.ping = True
+        fq = MyFinalizerQueue()
+        #
+        def main(argv):
+            glob.ping = False
+            lst1 = [X() for i in range(256)]
+            lst = [X() for i in range(3000)]
+            for i, x in enumerate(lst):
+                x.baz = i
+                fq.register_finalizer(x)
+            for i in range(3000):
+                lst[i] = None
+                if i % 300 == 150:
+                    rgc.collect()
+                revdb.stop_point()
+                j = i + glob.ping * 1000000
+                assert foobar(j) == j
+                if glob.ping:
+                    glob.ping = False
+                    total = 0
+                    while True:
+                        x = fq.next_dead()
+                        if x is None:
+                            break
+                        total = intmask(total * 3 + x.baz)
+                    assert foobar(total) == total
+            keepalive_until_here(lst1)
+            return 9
+        self.compile(main, [], backendopt=False)
+        out = self.run('Xx')
+        rdb = self.fetch_rdb([self.exename, 'Xx'])
+        uid_seen = set()
+        totals = []
+        for i in range(3000):
+            triggered = False
+            if rdb.is_special_packet():
+                time, = rdb.special_packet(ASYNC_FINALIZER_TRIGGER, 'q')
+                assert time == i + 1
+                triggered = True
+            j = rdb.next()
+            assert j == i + 1000000 * triggered
+            if triggered:
+                lst = []
+                while True:
+                    uid = intmask(rdb.next())
+                    if uid == -1:
+                        break
+                    assert uid > 0 and uid not in uid_seen
+                    uid_seen.add(uid)
+                    lst.append(uid)
+                totals.append((lst, intmask(rdb.next())))
+        x = rdb.next('q'); assert x == 3000    # number of stop points
+        #
+        assert 1500 <= len(uid_seen) <= 3000
+        d = dict(zip(sorted(uid_seen), range(len(uid_seen))))
+        for lst, expected in totals:
+            total = 0
+            for uid in lst:
+                total = intmask(total * 3 + d[uid])
+            assert total == expected
+
+    def test_finalizer_recorded(self):
+        py.test.skip("in-progress")
+        from rpython.rtyper.lltypesystem import lltype, rffi
+        from rpython.translator.tool.cbuild import ExternalCompilationInfo
+        eci = ExternalCompilationInfo(
+            pre_include_bits=["#define foobar(x) x\n"])
+        foobar = rffi.llexternal('foobar', [lltype.Signed], lltype.Signed,
+                                 compilation_info=eci)
+        class Glob:
+            pass
+        glob = Glob()
+        class X:
+            def __del__(self):
+                glob.count += 1
+        def main(argv):
+            glob.count = 0
+            lst = [X() for i in range(3000)]
+            x = -1
+            for i in range(3000):
+                lst[i] = None
+                if i % 300 == 150:
+                    rgc.collect()
+                revdb.stop_point()
+                x = glob.count
+                assert foobar(x) == x
+            print x
+            return 9
+        self.compile(main, [], backendopt=False)
+        out = self.run('Xx')
+        assert 1500 < int(out) <= 3000
+        rdb = self.fetch_rdb([self.exename, 'Xx'])
+        counts = [rdb.next() for i in range(3000)]
+        assert counts[0] >= 0
+        for i in range(len(counts)-1):
+            assert counts[i] <= counts[i + 1]
+        assert counts[-1] == int(out)
+        # write() call
+        x = rdb.next(); assert x == len(out)
+        x = rdb.next('i'); assert x == 0      # errno
+        x = rdb.next('q'); assert x == 3000    # number of stop points
+        assert rdb.done()
+
 
 class TestReplaying(InteractiveTests):
     expected_stop_points = 1


More information about the pypy-commit mailing list