[pypy-commit] stmgc gc-small-uniform: hg merge default

arigo noreply at buildbot.pypy.org
Tue May 13 17:53:15 CEST 2014


Author: Armin Rigo <arigo at tunes.org>
Branch: gc-small-uniform
Changeset: r1208:7cc0f05c1049
Date: 2014-05-13 16:31 +0200
http://bitbucket.org/pypy/stmgc/changeset/7cc0f05c1049/

Log:	hg merge default

diff too long, truncating to 2000 out of 2212 lines

diff --git a/c7/TODO b/c7/TODO
--- a/c7/TODO
+++ b/c7/TODO
@@ -11,3 +11,8 @@
 - fork() is done by copying the whole mmap non-lazily; improve.
 
 - contention.c: when pausing: should also tell other_pseg "please commit soon"
+
+- resharing: remap_file_pages on multiple pages at once; and madvise()
+  the unused pages away --- or maybe use consecutive addresses from the
+  lowest ones from segment N, instead of the page corresponding to the page
+  number in segment 0 (possibly a bit messy)
diff --git a/c7/demo/demo2.c b/c7/demo/demo2.c
--- a/c7/demo/demo2.c
+++ b/c7/demo/demo2.c
@@ -44,6 +44,16 @@
     visit((object_t **)&n->next);
 }
 
+void stmcb_commit_soon() {}
+
+static void expand_marker(char *base, uintptr_t odd_number,
+                          object_t *following_object,
+                          char *outputbuf, size_t outputbufsize)
+{
+    assert(following_object == NULL);
+    snprintf(outputbuf, outputbufsize, "<%p %lu>", base, odd_number);
+}
+
 
 nodeptr_t global_chained_list;
 
@@ -88,6 +98,18 @@
 
     STM_START_TRANSACTION(&stm_thread_local, here);
 
+    if (stm_thread_local.longest_marker_state != 0) {
+        fprintf(stderr, "[%p] marker %d for %.6f seconds:\n",
+                &stm_thread_local,
+                stm_thread_local.longest_marker_state,
+                stm_thread_local.longest_marker_time);
+        fprintf(stderr, "\tself:\t\"%s\"\n\tother:\t\"%s\"\n",
+                stm_thread_local.longest_marker_self,
+                stm_thread_local.longest_marker_other);
+        stm_thread_local.longest_marker_state = 0;
+        stm_thread_local.longest_marker_time = 0.0;
+    }
+
     nodeptr_t prev = initial;
     stm_read((objptr_t)prev);
 
@@ -194,15 +216,24 @@
 {
     int status;
     stm_register_thread_local(&stm_thread_local);
+    char *org = (char *)stm_thread_local.shadowstack;
 
     STM_PUSH_ROOT(stm_thread_local, global_chained_list);  /* remains forever in the shadow stack */
 
+    int loops = 0;
+
     while (check_sorted() == -1) {
+
+        STM_PUSH_MARKER(stm_thread_local, 2 * loops + 1, NULL);
+
         bubble_run();
+
+        STM_POP_MARKER(stm_thread_local);
+        loops++;
     }
 
     STM_POP_ROOT(stm_thread_local, global_chained_list);
-    assert(stm_thread_local.shadowstack == stm_thread_local.shadowstack_base);
+    OPT_ASSERT(org == (char *)stm_thread_local.shadowstack);
 
     unregister_thread_local();
     status = sem_post(&done); assert(status == 0);
@@ -245,6 +276,7 @@
 
     stm_setup();
     stm_register_thread_local(&stm_thread_local);
+    stmcb_expand_marker = expand_marker;
 
 
     setup_list();
diff --git a/c7/demo/demo_largemalloc.c b/c7/demo/demo_largemalloc.c
--- a/c7/demo/demo_largemalloc.c
+++ b/c7/demo/demo_largemalloc.c
@@ -23,6 +23,8 @@
     abort();
 }
 
+void stmcb_commit_soon() {}
+
 /************************************************************/
 
 #define ARENA_SIZE  (1024*1024*1024)
diff --git a/c7/demo/demo_random.c b/c7/demo/demo_random.c
--- a/c7/demo/demo_random.c
+++ b/c7/demo/demo_random.c
@@ -79,6 +79,8 @@
     assert(n->next == *last_next);
 }
 
+void stmcb_commit_soon() {}
+
 int get_rand(int max)
 {
     if (max == 0)
diff --git a/c7/demo/demo_simple.c b/c7/demo/demo_simple.c
--- a/c7/demo/demo_simple.c
+++ b/c7/demo/demo_simple.c
@@ -39,6 +39,8 @@
     visit((object_t **)&n->next);
 }
 
+void stmcb_commit_soon() {}
+
 
 
 static sem_t done;
@@ -50,6 +52,7 @@
 {
     int status;
     stm_register_thread_local(&stm_thread_local);
+    char *org = (char *)stm_thread_local.shadowstack;
     tl_counter = 0;
 
     object_t *tmp;
@@ -65,7 +68,7 @@
         i++;
     }
 
-    assert(stm_thread_local.shadowstack == stm_thread_local.shadowstack_base);
+    assert(org == (char *)stm_thread_local.shadowstack);
 
     stm_unregister_thread_local(&stm_thread_local);
     status = sem_post(&done); assert(status == 0);
diff --git a/c7/doc/marker.txt b/c7/doc/marker.txt
new file mode 100644
--- /dev/null
+++ b/c7/doc/marker.txt
@@ -0,0 +1,42 @@
+
+Reports
+=======
+
+- self-abort:
+    WRITE_WRITE_CONTENTION, INEVITABLE_CONTENTION:
+       marker in both threads, time lost by this thread
+    WRITE_READ_CONTENTION:
+       marker pointing back to the write, time lost by this thread
+
+- aborted by a different thread:
+    WRITE_WRITE_CONTENTION:
+       marker in both threads, time lost by this thread
+    WRITE_READ_CONTENTION:
+       remote marker pointing back to the write, time lost by this thread
+       (no local marker available to know where we've read the object from)
+    INEVITABLE_CONTENTION:
+       n/a
+
+- self-pausing:
+    same as self-abort, but reporting the time lost by pausing
+
+- waiting for a free segment:
+    - if we're waiting because of inevitability, report with a
+      marker and the time lost
+    - if we're just waiting because of no free segment, don't report it,
+      or maybe with only the total time lost and no marker
+
+- more internal reasons for cond_wait(), like synchronizing the threads,
+  should all be resolved quickly and are unlikely worth a report
+
+
+Internal Measurements
+=====================
+
+- use clock_gettime(CLOCK_MONOTONIC), it seems to be the fastest way
+  (less than 5 times slower than a RDTSC instruction, which is itself
+  not safe in the presence of threads migrating among CPUs)
+
+- record only the highest-time entry.  The user of the library is
+  responsible for getting and clearing it often enough if it wants
+  more details.
diff --git a/c7/stm/contention.c b/c7/stm/contention.c
--- a/c7/stm/contention.c
+++ b/c7/stm/contention.c
@@ -99,7 +99,8 @@
 
 
 static void contention_management(uint8_t other_segment_num,
-                                  enum contention_kind_e kind)
+                                  enum contention_kind_e kind,
+                                  object_t *obj)
 {
     assert(_has_mutex());
     assert(other_segment_num != STM_SEGMENT->segment_num);
@@ -161,10 +162,12 @@
              itself already paused here.
         */
         contmgr.other_pseg->signal_when_done = true;
+        marker_contention(kind, false, other_segment_num, obj);
 
         change_timing_state(wait_category);
 
-        /* XXX should also tell other_pseg "please commit soon" */
+        /* tell the other to commit ASAP */
+        signal_other_to_commit_soon(contmgr.other_pseg);
 
         dprintf(("pausing...\n"));
         cond_signal(C_AT_SAFE_POINT);
@@ -176,12 +179,22 @@
         if (must_abort())
             abort_with_mutex();
 
-        change_timing_state(STM_TIME_RUN_CURRENT);
+        struct stm_priv_segment_info_s *pseg =
+            get_priv_segment(STM_SEGMENT->segment_num);
+        double elapsed =
+            change_timing_state_tl(pseg->pub.running_thread,
+                                   STM_TIME_RUN_CURRENT);
+        marker_copy(pseg->pub.running_thread, pseg,
+                    wait_category, elapsed);
     }
 
     else if (!contmgr.abort_other) {
+        /* tell the other to commit ASAP, since it causes aborts */
+        signal_other_to_commit_soon(contmgr.other_pseg);
+
         dprintf(("abort in contention\n"));
         STM_SEGMENT->nursery_end = abort_category;
+        marker_contention(kind, false, other_segment_num, obj);
         abort_with_mutex();
     }
 
@@ -189,6 +202,7 @@
         /* We have to signal the other thread to abort, and wait until
            it does. */
         contmgr.other_pseg->pub.nursery_end = abort_category;
+        marker_contention(kind, true, other_segment_num, obj);
 
         int sp = contmgr.other_pseg->safe_point;
         switch (sp) {
@@ -256,10 +270,18 @@
             abort_data_structures_from_segment_num(other_segment_num);
         }
         dprintf(("killed other thread\n"));
+
+        /* we should commit soon, we caused an abort */
+        //signal_other_to_commit_soon(get_priv_segment(STM_SEGMENT->segment_num));
+        if (!STM_PSEGMENT->signalled_to_commit_soon) {
+            STM_PSEGMENT->signalled_to_commit_soon = true;
+            stmcb_commit_soon();
+        }
     }
 }
 
-static void write_write_contention_management(uintptr_t lock_idx)
+static void write_write_contention_management(uintptr_t lock_idx,
+                                              object_t *obj)
 {
     s_mutex_lock();
 
@@ -270,7 +292,7 @@
         assert(get_priv_segment(other_segment_num)->write_lock_num ==
                prev_owner);
 
-        contention_management(other_segment_num, WRITE_WRITE_CONTENTION);
+        contention_management(other_segment_num, WRITE_WRITE_CONTENTION, obj);
 
         /* now we return into _stm_write_slowpath() and will try again
            to acquire the write lock on our object. */
@@ -279,12 +301,13 @@
     s_mutex_unlock();
 }
 
-static void write_read_contention_management(uint8_t other_segment_num)
+static void write_read_contention_management(uint8_t other_segment_num,
+                                             object_t *obj)
 {
-    contention_management(other_segment_num, WRITE_READ_CONTENTION);
+    contention_management(other_segment_num, WRITE_READ_CONTENTION, obj);
 }
 
 static void inevitable_contention_management(uint8_t other_segment_num)
 {
-    contention_management(other_segment_num, INEVITABLE_CONTENTION);
+    contention_management(other_segment_num, INEVITABLE_CONTENTION, NULL);
 }
diff --git a/c7/stm/contention.h b/c7/stm/contention.h
--- a/c7/stm/contention.h
+++ b/c7/stm/contention.h
@@ -1,10 +1,13 @@
 
-static void write_write_contention_management(uintptr_t lock_idx);
-static void write_read_contention_management(uint8_t other_segment_num);
+static void write_write_contention_management(uintptr_t lock_idx,
+                                              object_t *obj);
+static void write_read_contention_management(uint8_t other_segment_num,
+                                             object_t *obj);
 static void inevitable_contention_management(uint8_t other_segment_num);
 
 static inline bool is_abort(uintptr_t nursery_end) {
-    return (nursery_end <= _STM_NSE_SIGNAL_MAX && nursery_end != NSE_SIGPAUSE);
+    return (nursery_end <= _STM_NSE_SIGNAL_MAX && nursery_end != NSE_SIGPAUSE
+            && nursery_end != NSE_SIGCOMMITSOON);
 }
 
 static inline bool is_aborting_now(uint8_t other_segment_num) {
diff --git a/c7/stm/core.c b/c7/stm/core.c
--- a/c7/stm/core.c
+++ b/c7/stm/core.c
@@ -14,13 +14,10 @@
 #define EVENTUALLY(condition)                                   \
     {                                                           \
         if (!(condition)) {                                     \
-            int _i;                                             \
-            for (_i = 1; _i <= NB_SEGMENTS; _i++)               \
-                spinlock_acquire(lock_pages_privatizing[_i]);   \
+            acquire_privatization_lock();                       \
             if (!(condition))                                   \
                 stm_fatalerror("fails: " #condition);           \
-            for (_i = 1; _i <= NB_SEGMENTS; _i++)               \
-                spinlock_release(lock_pages_privatizing[_i]);   \
+            release_privatization_lock();                       \
         }                                                       \
     }
 #endif
@@ -76,9 +73,15 @@
     assert(lock_idx < sizeof(write_locks));
  retry:
     if (write_locks[lock_idx] == 0) {
+        /* A lock to prevent reading garbage from
+           lookup_other_thread_recorded_marker() */
+        acquire_marker_lock(STM_SEGMENT->segment_base);
+
         if (UNLIKELY(!__sync_bool_compare_and_swap(&write_locks[lock_idx],
-                                                   0, lock_num)))
+                                                   0, lock_num))) {
+            release_marker_lock(STM_SEGMENT->segment_base);
             goto retry;
+        }
 
         dprintf_test(("write_slowpath %p -> mod_old\n", obj));
 
@@ -86,6 +89,15 @@
            Add it to the list 'modified_old_objects'. */
         LIST_APPEND(STM_PSEGMENT->modified_old_objects, obj);
 
+        /* Add the current marker, recording where we wrote to this object */
+        uintptr_t marker[2];
+        marker_fetch(STM_SEGMENT->running_thread, marker);
+        STM_PSEGMENT->modified_old_objects_markers =
+            list_append2(STM_PSEGMENT->modified_old_objects_markers,
+                         marker[0], marker[1]);
+
+        release_marker_lock(STM_SEGMENT->segment_base);
+
         /* We need to privatize the pages containing the object, if they
            are still SHARED_PAGE.  The common case is that there is only
            one page in total. */
@@ -127,7 +139,7 @@
     else {
         /* call the contention manager, and then retry (unless we were
            aborted). */
-        write_write_contention_management(lock_idx);
+        write_write_contention_management(lock_idx, obj);
         goto retry;
     }
 
@@ -195,7 +207,13 @@
     assert(STM_PSEGMENT->transaction_state == TS_NONE);
     change_timing_state(STM_TIME_RUN_CURRENT);
     STM_PSEGMENT->start_time = tl->_timing_cur_start;
+    STM_PSEGMENT->signalled_to_commit_soon = false;
     STM_PSEGMENT->safe_point = SP_RUNNING;
+#ifndef NDEBUG
+    STM_PSEGMENT->marker_inev[1] = 99999999999999999L;
+#endif
+    if (jmpbuf == NULL)
+        marker_fetch_inev();
     STM_PSEGMENT->transaction_state = (jmpbuf != NULL ? TS_REGULAR
                                                       : TS_INEVITABLE);
     STM_SEGMENT->jmpbuf_ptr = jmpbuf;
@@ -223,12 +241,17 @@
     }
 
     assert(list_is_empty(STM_PSEGMENT->modified_old_objects));
+    assert(list_is_empty(STM_PSEGMENT->modified_old_objects_markers));
     assert(list_is_empty(STM_PSEGMENT->young_weakrefs));
     assert(tree_is_cleared(STM_PSEGMENT->young_outside_nursery));
     assert(tree_is_cleared(STM_PSEGMENT->nursery_objects_shadows));
     assert(tree_is_cleared(STM_PSEGMENT->callbacks_on_abort));
     assert(STM_PSEGMENT->objects_pointing_to_nursery == NULL);
     assert(STM_PSEGMENT->large_overflow_objects == NULL);
+#ifndef NDEBUG
+    /* this should not be used when objects_pointing_to_nursery == NULL */
+    STM_PSEGMENT->modified_old_objects_markers_num_old = 99999999999999999L;
+#endif
 
     check_nursery_at_transaction_start();
 }
@@ -263,7 +286,7 @@
             ({
                 if (was_read_remote(remote_base, item, remote_version)) {
                     /* A write-read conflict! */
-                    write_read_contention_management(i);
+                    write_read_contention_management(i, item);
 
                     /* If we reach this point, we didn't abort, but maybe we
                        had to wait for the other thread to commit.  If we
@@ -359,12 +382,15 @@
        It is first copied into the shared pages, and then into other
        segments' own private pages.  (The second part might be done
        later; call synchronize_objects_flush() to flush this queue.)
+
+       Must be called with the privatization lock acquired.
     */
     assert(!_is_young(obj));
     assert(obj->stm_flags & GCFLAG_WRITE_BARRIER);
     ssize_t obj_size = stmcb_size_rounded_up(
         (struct object_s *)REAL_ADDRESS(STM_SEGMENT->segment_base, obj));
     OPT_ASSERT(obj_size >= 16);
+    assert(STM_PSEGMENT->privatization_lock == 1);
 
     if (LIKELY(is_small_uniform(obj))) {
         _synchronize_fragment((stm_char *)obj, obj_size);
@@ -444,13 +470,16 @@
     if (STM_PSEGMENT->large_overflow_objects == NULL)
         return;
 
+    acquire_privatization_lock();
     LIST_FOREACH_R(STM_PSEGMENT->large_overflow_objects, object_t *,
                    synchronize_object_enqueue(item));
     synchronize_objects_flush();
+    release_privatization_lock();
 }
 
 static void push_modified_to_other_segments(void)
 {
+    acquire_privatization_lock();
     LIST_FOREACH_R(
         STM_PSEGMENT->modified_old_objects,
         object_t * /*item*/,
@@ -470,9 +499,11 @@
                private pages as needed */
             synchronize_object_enqueue(item);
         }));
+    release_privatization_lock();
 
     synchronize_objects_flush();
     list_clear(STM_PSEGMENT->modified_old_objects);
+    list_clear(STM_PSEGMENT->modified_old_objects_markers);
 }
 
 static void _finish_transaction(int attribute_to)
@@ -614,6 +645,7 @@
         }));
 
     list_clear(pseg->modified_old_objects);
+    list_clear(pseg->modified_old_objects_markers);
 }
 
 static void abort_data_structures_from_segment_num(int segment_num)
@@ -638,6 +670,10 @@
                        (int)pseg->transaction_state);
     }
 
+    /* if we don't have marker information already, look up and preserve
+       the marker information from the shadowstack as a string */
+    marker_default_for_abort(pseg);
+
     /* throw away the content of the nursery */
     long bytes_in_nursery = throw_away_nursery(pseg);
 
@@ -648,6 +684,7 @@
        value before the transaction start */
     stm_thread_local_t *tl = pseg->pub.running_thread;
     assert(tl->shadowstack >= pseg->shadowstack_at_start_of_transaction);
+    pseg->shadowstack_at_abort = tl->shadowstack;
     tl->shadowstack = pseg->shadowstack_at_start_of_transaction;
     tl->thread_local_obj = pseg->threadlocal_at_start_of_transaction;
     tl->last_abort__bytes_in_nursery = bytes_in_nursery;
@@ -719,6 +756,7 @@
     if (STM_PSEGMENT->transaction_state == TS_REGULAR) {
         dprintf(("become_inevitable: %s\n", msg));
 
+        marker_fetch_inev();
         wait_for_end_of_inevitable_transaction(NULL);
         STM_PSEGMENT->transaction_state = TS_INEVITABLE;
         STM_SEGMENT->jmpbuf_ptr = NULL;
diff --git a/c7/stm/core.h b/c7/stm/core.h
--- a/c7/stm/core.h
+++ b/c7/stm/core.h
@@ -75,9 +75,17 @@
     /* List of old objects (older than the current transaction) that the
        current transaction attempts to modify.  This is used to track
        the STM status: they are old objects that where written to and
-       that need to be copied to other segments upon commit. */
+       that need to be copied to other segments upon commit.  Note that
+       every object takes three list items: the object, and two words for
+       the location marker. */
     struct list_s *modified_old_objects;
 
+    /* For each entry in 'modified_old_objects', we have two entries
+       in the following list, which give the marker at the time we added
+       the entry to modified_old_objects. */
+    struct list_s *modified_old_objects_markers;
+    uintptr_t modified_old_objects_markers_num_old;
+
     /* List of out-of-nursery objects that may contain pointers to
        nursery objects.  This is used to track the GC status: they are
        all objects outside the nursery on which an stm_write() occurred
@@ -145,10 +153,30 @@
     /* For sleeping contention management */
     bool signal_when_done;
 
+    /* This lock is acquired when that segment calls synchronize_object_now.
+       On the rare event of a page_privatize(), the latter will acquire
+       all the locks in all segments.  Otherwise, for the common case,
+       it's cheap.  (The set of all 'privatization_lock' in all segments
+       works like one single read-write lock, with page_privatize() acquiring
+       the write lock; but this variant is more efficient for the case of
+       many reads / rare writes.) */
+    uint8_t privatization_lock;
+
+    /* This lock is acquired when we mutate 'modified_old_objects' but
+       we don't have the global mutex.  It is also acquired during minor
+       collection.  It protects against a different thread that tries to
+       get this segment's marker corresponding to some object, or to
+       expand the marker into a full description. */
+    uint8_t marker_lock;
+
     /* In case of abort, we restore the 'shadowstack' field and the
        'thread_local_obj' field. */
     struct stm_shadowentry_s *shadowstack_at_start_of_transaction;
     object_t *threadlocal_at_start_of_transaction;
+    struct stm_shadowentry_s *shadowstack_at_abort;
+
+    /* Already signalled to commit soon: */
+    bool signalled_to_commit_soon;
 
     /* For debugging */
 #ifndef NDEBUG
@@ -163,6 +191,11 @@
     stm_char *sq_fragments[SYNC_QUEUE_SIZE];
     int sq_fragsizes[SYNC_QUEUE_SIZE];
     int sq_len;
+
+    /* Temporarily stores the marker information */
+    char marker_self[_STM_MARKER_LEN];
+    char marker_other[_STM_MARKER_LEN];
+    uintptr_t marker_inev[2];  /* marker where this thread became inevitable */
 };
 
 enum /* safe_point */ {
@@ -185,6 +218,7 @@
 static
 #endif
        char *stm_object_pages;
+static int stm_object_pages_fd;
 static stm_thread_local_t *stm_all_thread_locals = NULL;
 
 static uint8_t write_locks[WRITELOCK_END - WRITELOCK_START];
@@ -236,3 +270,31 @@
 static void copy_object_to_shared(object_t *obj, int source_segment_num);
 static void synchronize_object_enqueue(object_t *obj);
 static void synchronize_objects_flush(void);
+
+static inline void acquire_privatization_lock(void)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(STM_SEGMENT->segment_base,
+                                            &STM_PSEGMENT->privatization_lock);
+    spinlock_acquire(*lock);
+}
+
+static inline void release_privatization_lock(void)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(STM_SEGMENT->segment_base,
+                                            &STM_PSEGMENT->privatization_lock);
+    spinlock_release(*lock);
+}
+
+static inline void acquire_marker_lock(char *segment_base)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(segment_base,
+                                            &STM_PSEGMENT->marker_lock);
+    spinlock_acquire(*lock);
+}
+
+static inline void release_marker_lock(char *segment_base)
+{
+    uint8_t *lock = (uint8_t *)REAL_ADDRESS(segment_base,
+                                            &STM_PSEGMENT->marker_lock);
+    spinlock_release(*lock);
+}
diff --git a/c7/stm/forksupport.c b/c7/stm/forksupport.c
--- a/c7/stm/forksupport.c
+++ b/c7/stm/forksupport.c
@@ -8,14 +8,10 @@
 
 
 static char *fork_big_copy = NULL;
+static int fork_big_copy_fd;
 static stm_thread_local_t *fork_this_tl;
 static bool fork_was_in_transaction;
 
-static char *setup_mmap(char *reason);            /* forward, in setup.c */
-static void setup_protection_settings(void);      /* forward, in setup.c */
-static pthread_t *_get_cpth(stm_thread_local_t *);/* forward, in setup.c */
-
-
 static bool page_is_null(char *p)
 {
     long *q = (long *)p;
@@ -74,7 +70,8 @@
     /* Make a new mmap at some other address, but of the same size as
        the standard mmap at stm_object_pages
     */
-    char *big_copy = setup_mmap("stmgc's fork support");
+    int big_copy_fd;
+    char *big_copy = setup_mmap("stmgc's fork support", &big_copy_fd);
 
     /* Copy each of the segment infos into the new mmap, nurseries,
        and associated read markers
@@ -139,6 +136,7 @@
 
     assert(fork_big_copy == NULL);
     fork_big_copy = big_copy;
+    fork_big_copy_fd = big_copy_fd;
     fork_this_tl = this_tl;
     fork_was_in_transaction = was_in_transaction;
 
@@ -163,6 +161,7 @@
     assert(fork_big_copy != NULL);
     munmap(fork_big_copy, TOTAL_MEMORY);
     fork_big_copy = NULL;
+    close_fd_mmap(fork_big_copy_fd);
     bool was_in_transaction = fork_was_in_transaction;
 
     s_mutex_unlock();
@@ -214,6 +213,8 @@
     if (res != stm_object_pages)
         stm_fatalerror("after fork: mremap failed: %m");
     fork_big_copy = NULL;
+    close_fd_mmap(stm_object_pages_fd);
+    stm_object_pages_fd = fork_big_copy_fd;
 
     /* Unregister all other stm_thread_local_t, mostly as a way to free
        the memory used by the shadowstacks
diff --git a/c7/stm/gcpage.c b/c7/stm/gcpage.c
--- a/c7/stm/gcpage.c
+++ b/c7/stm/gcpage.c
@@ -49,17 +49,20 @@
     /* uncommon case: need to initialize some more pages */
     spinlock_acquire(lock_growth_large);
 
-    if (addr + size > uninitialized_page_start) {
+    char *start = uninitialized_page_start;
+    if (addr + size > start) {
         uintptr_t npages;
-        npages = (addr + size - uninitialized_page_start) / 4096UL;
+        npages = (addr + size - start) / 4096UL;
         npages += GCPAGE_NUM_PAGES;
-        if (uninitialized_page_stop - uninitialized_page_start <
-                npages * 4096UL) {
+        if (uninitialized_page_stop - start < npages * 4096UL) {
             stm_fatalerror("out of memory!");   /* XXX */
         }
-        setup_N_pages(uninitialized_page_start, npages);
-        __sync_synchronize();
-        uninitialized_page_start += npages * 4096UL;
+        setup_N_pages(start, npages);
+        if (!__sync_bool_compare_and_swap(&uninitialized_page_start,
+                                          start,
+                                          start + npages * 4096UL)) {
+            stm_fatalerror("uninitialized_page_start changed?");
+        }
     }
     spinlock_release(lock_growth_large);
     return addr;
@@ -336,8 +339,8 @@
         struct stm_shadowentry_s *current = tl->shadowstack;
         struct stm_shadowentry_s *base = tl->shadowstack_base;
         while (current-- != base) {
-            assert(current->ss != (object_t *)-1);
-            mark_visit_object(current->ss, segment_base);
+            if ((((uintptr_t)current->ss) & 3) == 0)
+                mark_visit_object(current->ss, segment_base);
         }
         mark_visit_object(tl->thread_local_obj, segment_base);
 
@@ -375,6 +378,23 @@
     }
 }
 
+static void mark_visit_from_markers(void)
+{
+    long j;
+    for (j = 1; j <= NB_SEGMENTS; j++) {
+        char *base = get_segment_base(j);
+        struct list_s *lst = get_priv_segment(j)->modified_old_objects_markers;
+        uintptr_t i;
+        for (i = list_count(lst); i > 0; i -= 2) {
+            mark_visit_object((object_t *)list_item(lst, i - 1), base);
+        }
+        if (get_priv_segment(j)->transaction_state == TS_INEVITABLE) {
+            uintptr_t marker_inev_obj = get_priv_segment(j)->marker_inev[1];
+            mark_visit_object((object_t *)marker_inev_obj, base);
+        }
+    }
+}
+
 static void clean_up_segment_lists(void)
 {
     long i;
@@ -477,6 +497,7 @@
     /* marking */
     LIST_CREATE(mark_objects_to_trace);
     mark_visit_from_modified_objects();
+    mark_visit_from_markers();
     mark_visit_from_roots();
     LIST_FREE(mark_objects_to_trace);
 
diff --git a/c7/stm/largemalloc.c b/c7/stm/largemalloc.c
--- a/c7/stm/largemalloc.c
+++ b/c7/stm/largemalloc.c
@@ -353,6 +353,9 @@
     mscan->size = request_size;
     mscan->prev_size = BOTH_CHUNKS_USED;
     increment_total_allocated(request_size + LARGE_MALLOC_OVERHEAD);
+#ifndef NDEBUG
+    memset((char *)&mscan->d, 0xda, request_size);
+#endif
 
     lm_unlock();
 
diff --git a/c7/stm/list.h b/c7/stm/list.h
--- a/c7/stm/list.h
+++ b/c7/stm/list.h
@@ -33,6 +33,18 @@
 
 #define LIST_APPEND(lst, e)   ((lst) = list_append((lst), (uintptr_t)(e)))
 
+static inline struct list_s *list_append2(struct list_s *lst,
+                                          uintptr_t item0, uintptr_t item1)
+{
+    uintptr_t index = lst->count;
+    lst->count += 2;
+    if (UNLIKELY(index >= lst->last_allocated))
+        lst = _list_grow(lst, index + 1);
+    lst->items[index + 0] = item0;
+    lst->items[index + 1] = item1;
+    return lst;
+}
+
 
 static inline void list_clear(struct list_s *lst)
 {
@@ -66,6 +78,11 @@
     lst->items[index] = newitem;
 }
 
+static inline uintptr_t *list_ptr_to_item(struct list_s *lst, uintptr_t index)
+{
+    return &lst->items[index];
+}
+
 #define LIST_FOREACH_R(lst, TYPE, CODE)         \
     do {                                        \
         struct list_s *_lst = (lst);            \
diff --git a/c7/stm/marker.c b/c7/stm/marker.c
new file mode 100644
--- /dev/null
+++ b/c7/stm/marker.c
@@ -0,0 +1,198 @@
+#ifndef _STM_CORE_H_
+# error "must be compiled via stmgc.c"
+#endif
+
+
+void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                            object_t *following_object,
+                            char *outputbuf, size_t outputbufsize);
+
+void (*stmcb_debug_print)(const char *cause, double time,
+                          const char *marker);
+
+
+static void marker_fetch(stm_thread_local_t *tl, uintptr_t marker[2])
+{
+    /* fetch the current marker from the tl's shadow stack,
+       and return it in 'marker[2]'. */
+    struct stm_shadowentry_s *current = tl->shadowstack - 1;
+    struct stm_shadowentry_s *base = tl->shadowstack_base;
+
+    /* The shadowstack_base contains STM_STACK_MARKER_OLD, which is
+       a convenient stopper for the loop below but which shouldn't
+       be returned. */
+    assert(base->ss == (object_t *)STM_STACK_MARKER_OLD);
+
+    while (!(((uintptr_t)current->ss) & 1)) {
+        current--;
+        assert(current >= base);
+    }
+    if (current != base) {
+        /* found the odd marker */
+        marker[0] = (uintptr_t)current[0].ss;
+        marker[1] = (uintptr_t)current[1].ss;
+    }
+    else {
+        /* no marker found */
+        marker[0] = 0;
+        marker[1] = 0;
+    }
+}
+
+static void marker_expand(uintptr_t marker[2], char *segment_base,
+                          char *outmarker)
+{
+    /* Expand the marker given by 'marker[2]' into a full string.  This
+       works assuming that the marker was produced inside the segment
+       given by 'segment_base'.  If that's from a different thread, you
+       must first acquire the corresponding 'marker_lock'. */
+    assert(_has_mutex());
+    outmarker[0] = 0;
+    if (marker[0] == 0)
+        return;   /* no marker entry found */
+    if (stmcb_expand_marker != NULL) {
+        stmcb_expand_marker(segment_base, marker[0], (object_t *)marker[1],
+                            outmarker, _STM_MARKER_LEN);
+    }
+}
+
+static void marker_default_for_abort(struct stm_priv_segment_info_s *pseg)
+{
+    if (pseg->marker_self[0] != 0)
+        return;   /* already collected an entry */
+
+    uintptr_t marker[2];
+    marker_fetch(pseg->pub.running_thread, marker);
+    marker_expand(marker, pseg->pub.segment_base, pseg->marker_self);
+    pseg->marker_other[0] = 0;
+}
+
+char *_stm_expand_marker(void)
+{
+    /* for tests only! */
+    static char _result[_STM_MARKER_LEN];
+    uintptr_t marker[2];
+    _result[0] = 0;
+    s_mutex_lock();
+    marker_fetch(STM_SEGMENT->running_thread, marker);
+    marker_expand(marker, STM_SEGMENT->segment_base, _result);
+    s_mutex_unlock();
+    return _result;
+}
+
+static void marker_copy(stm_thread_local_t *tl,
+                        struct stm_priv_segment_info_s *pseg,
+                        enum stm_time_e attribute_to, double time)
+{
+    /* Copies the marker information from pseg to tl.  This is called
+       indirectly from abort_with_mutex(), but only if the lost time is
+       greater than that of the previous recorded marker.  By contrast,
+       pseg->marker_self has been filled already in all cases.  The
+       reason for the two steps is that we must fill pseg->marker_self
+       earlier than now (some objects may be GCed), but we only know
+       here the total time it gets attributed.
+    */
+    if (stmcb_debug_print) {
+        stmcb_debug_print(timer_names[attribute_to], time, pseg->marker_self);
+    }
+    if (time * 0.99 > tl->longest_marker_time) {
+        tl->longest_marker_state = attribute_to;
+        tl->longest_marker_time = time;
+        memcpy(tl->longest_marker_self, pseg->marker_self, _STM_MARKER_LEN);
+        memcpy(tl->longest_marker_other, pseg->marker_other, _STM_MARKER_LEN);
+    }
+    pseg->marker_self[0] = 0;
+    pseg->marker_other[0] = 0;
+}
+
+static void marker_fetch_obj_write(uint8_t in_segment_num, object_t *obj,
+                                   uintptr_t marker[2])
+{
+    assert(_has_mutex());
+
+    /* here, we acquired the other thread's marker_lock, which means that:
+
+       (1) it has finished filling 'modified_old_objects' after it sets
+           up the write_locks[] value that we're conflicting with
+
+       (2) it is not mutating 'modified_old_objects' right now (we have
+           the global mutex_lock at this point too).
+    */
+    long i;
+    struct stm_priv_segment_info_s *pseg = get_priv_segment(in_segment_num);
+    struct list_s *mlst = pseg->modified_old_objects;
+    struct list_s *mlstm = pseg->modified_old_objects_markers;
+    for (i = list_count(mlst); --i >= 0; ) {
+        if (list_item(mlst, i) == (uintptr_t)obj) {
+            assert(list_count(mlstm) == 2 * list_count(mlst));
+            marker[0] = list_item(mlstm, i * 2 + 0);
+            marker[1] = list_item(mlstm, i * 2 + 1);
+            return;
+        }
+    }
+    marker[0] = 0;
+    marker[1] = 0;
+}
+
+static void marker_contention(int kind, bool abort_other,
+                              uint8_t other_segment_num, object_t *obj)
+{
+    uintptr_t self_marker[2];
+    uintptr_t other_marker[2];
+    struct stm_priv_segment_info_s *my_pseg, *other_pseg;
+
+    my_pseg = get_priv_segment(STM_SEGMENT->segment_num);
+    other_pseg = get_priv_segment(other_segment_num);
+
+    char *my_segment_base = STM_SEGMENT->segment_base;
+    char *other_segment_base = get_segment_base(other_segment_num);
+
+    acquire_marker_lock(other_segment_base);
+
+    /* Collect the location for myself.  It's usually the current
+       location, except in a write-read abort, in which case it's the
+       older location of the write. */
+    if (kind == WRITE_READ_CONTENTION)
+        marker_fetch_obj_write(my_pseg->pub.segment_num, obj, self_marker);
+    else
+        marker_fetch(my_pseg->pub.running_thread, self_marker);
+
+    /* Expand this location into either my_pseg->marker_self or
+       other_pseg->marker_other, depending on who aborts. */
+    marker_expand(self_marker, my_segment_base,
+                  abort_other ? other_pseg->marker_other
+                              : my_pseg->marker_self);
+
+    /* For some categories, we can also collect the relevant information
+       for the other segment. */
+    char *outmarker = abort_other ? other_pseg->marker_self
+                                  : my_pseg->marker_other;
+    switch (kind) {
+    case WRITE_WRITE_CONTENTION:
+        marker_fetch_obj_write(other_segment_num, obj, other_marker);
+        marker_expand(other_marker, other_segment_base, outmarker);
+        break;
+    case INEVITABLE_CONTENTION:
+        assert(abort_other == false);
+        other_marker[0] = other_pseg->marker_inev[0];
+        other_marker[1] = other_pseg->marker_inev[1];
+        marker_expand(other_marker, other_segment_base, outmarker);
+        break;
+    case WRITE_READ_CONTENTION:
+        strcpy(outmarker, "<read at unknown location>");
+        break;
+    default:
+        outmarker[0] = 0;
+        break;
+    }
+
+    release_marker_lock(other_segment_base);
+}
+
+static void marker_fetch_inev(void)
+{
+    uintptr_t marker[2];
+    marker_fetch(STM_SEGMENT->running_thread, marker);
+    STM_PSEGMENT->marker_inev[0] = marker[0];
+    STM_PSEGMENT->marker_inev[1] = marker[1];
+}
diff --git a/c7/stm/marker.h b/c7/stm/marker.h
new file mode 100644
--- /dev/null
+++ b/c7/stm/marker.h
@@ -0,0 +1,12 @@
+
+static void marker_fetch(stm_thread_local_t *tl, uintptr_t marker[2]);
+static void marker_fetch_inev(void);
+static void marker_expand(uintptr_t marker[2], char *segment_base,
+                          char *outmarker);
+static void marker_default_for_abort(struct stm_priv_segment_info_s *pseg);
+static void marker_copy(stm_thread_local_t *tl,
+                        struct stm_priv_segment_info_s *pseg,
+                        enum stm_time_e attribute_to, double time);
+
+static void marker_contention(int kind, bool abort_other,
+                              uint8_t other_segment_num, object_t *obj);
diff --git a/c7/stm/nursery.c b/c7/stm/nursery.c
--- a/c7/stm/nursery.c
+++ b/c7/stm/nursery.c
@@ -152,9 +152,29 @@
     stm_thread_local_t *tl = STM_SEGMENT->running_thread;
     struct stm_shadowentry_s *current = tl->shadowstack;
     struct stm_shadowentry_s *base = tl->shadowstack_base;
-    while (current-- != base) {
-        assert(current->ss != (object_t *)-1);
-        minor_trace_if_young(&current->ss);
+    while (1) {
+        --current;
+        OPT_ASSERT(current >= base);
+
+        uintptr_t x = (uintptr_t)current->ss;
+
+        if ((x & 3) == 0) {
+            /* the stack entry is a regular pointer (possibly NULL) */
+            minor_trace_if_young(&current->ss);
+        }
+        else if (x == STM_STACK_MARKER_NEW) {
+            /* the marker was not already seen: mark it as seen,
+               but continue looking more deeply in the shadowstack */
+            current->ss = (object_t *)STM_STACK_MARKER_OLD;
+        }
+        else if (x == STM_STACK_MARKER_OLD) {
+            /* the marker was already seen: we can stop the
+               root stack tracing at this point */
+            break;
+        }
+        else {
+            /* it is an odd-valued marker, ignore */
+        }
     }
     minor_trace_if_young(&tl->thread_local_obj);
 }
@@ -184,6 +204,7 @@
 
         _collect_now(obj);
 
+        XXX acquire_privatization_lock(); release_privatization_lock(); ?
         synchronize_object_enqueue(obj);
 
         /* the list could have moved while appending */
@@ -199,6 +220,24 @@
                    _collect_now(item));
 }
 
+static void collect_roots_from_markers(uintptr_t num_old)
+{
+    /* visit the marker objects */
+    struct list_s *mlst = STM_PSEGMENT->modified_old_objects_markers;
+    STM_PSEGMENT->modified_old_objects_markers_num_old = list_count(mlst);
+    uintptr_t i, total = list_count(mlst);
+    assert((total & 1) == 0);
+    for (i = num_old + 1; i < total; i += 2) {
+        minor_trace_if_young((object_t **)list_ptr_to_item(mlst, i));
+    }
+    if (STM_PSEGMENT->transaction_state == TS_INEVITABLE) {
+        uintptr_t *pmarker_inev_obj = (uintptr_t *)
+            REAL_ADDRESS(STM_SEGMENT->segment_base,
+                         &STM_PSEGMENT->marker_inev[1]);
+        minor_trace_if_young((object_t **)pmarker_inev_obj);
+    }
+}
+
 static size_t throw_away_nursery(struct stm_priv_segment_info_s *pseg)
 {
     /* reset the nursery by zeroing it */
@@ -207,6 +246,11 @@
 
     realnursery = REAL_ADDRESS(pseg->pub.segment_base, _stm_nursery_start);
     nursery_used = pseg->pub.nursery_current - (stm_char *)_stm_nursery_start;
+    if (nursery_used > NB_NURSERY_PAGES * 4096) {
+        /* possible in rare cases when the program artificially advances
+           its own nursery_current */
+        nursery_used = NB_NURSERY_PAGES * 4096;
+    }
     OPT_ASSERT((nursery_used & 7) == 0);
     memset(realnursery, 0, nursery_used);
 
@@ -248,8 +292,16 @@
 
     dprintf(("minor_collection commit=%d\n", (int)commit));
 
+    acquire_marker_lock(STM_SEGMENT->segment_base);
+
     STM_PSEGMENT->minor_collect_will_commit_now = commit;
     if (!commit) {
+        /* We should commit soon, probably. This is kind of a
+           workaround for the broken stm_should_break_transaction of
+           pypy that doesn't want to commit any more after a minor
+           collection. It may, however, always be a good idea... */
+        stmcb_commit_soon();
+
         /* 'STM_PSEGMENT->overflow_number' is used now by this collection,
            in the sense that it's copied to the overflow objects */
         STM_PSEGMENT->overflow_number_has_been_used = true;
@@ -263,6 +315,7 @@
     /* All the objects we move out of the nursery become "overflow"
        objects.  We use the list 'objects_pointing_to_nursery'
        to hold the ones we didn't trace so far. */
+    uintptr_t num_old;
     if (STM_PSEGMENT->objects_pointing_to_nursery == NULL) {
         STM_PSEGMENT->objects_pointing_to_nursery = list_create();
 
@@ -272,11 +325,15 @@
            into objects_pointing_to_nursery, but instead we use the
            following shortcut */
         collect_modified_old_objects();
+        num_old = 0;
     }
     else {
+        num_old = STM_PSEGMENT->modified_old_objects_markers_num_old;
         abort();  // handle specially the objects_pointing_to_nursery already there
     }
 
+    collect_roots_from_markers(num_old);
+
     collect_roots_in_nursery();
 
     collect_oldrefs_to_nursery();
@@ -288,6 +345,8 @@
 
     assert(MINOR_NOTHING_TO_DO(STM_PSEGMENT));
     assert(list_is_empty(STM_PSEGMENT->objects_pointing_to_nursery));
+
+    release_marker_lock(STM_SEGMENT->segment_base);
 }
 
 static void minor_collection(bool commit)
diff --git a/c7/stm/nursery.h b/c7/stm/nursery.h
--- a/c7/stm/nursery.h
+++ b/c7/stm/nursery.h
@@ -1,6 +1,7 @@
 
 /* '_stm_nursery_section_end' is either NURSERY_END or NSE_SIGxxx */
 #define NSE_SIGPAUSE   STM_TIME_WAIT_OTHER
+#define NSE_SIGCOMMITSOON   STM_TIME_SYNC_COMMIT_SOON
 
 
 static uint32_t highest_overflow_number;
diff --git a/c7/stm/pages.c b/c7/stm/pages.c
--- a/c7/stm/pages.c
+++ b/c7/stm/pages.c
@@ -81,9 +81,18 @@
        can only be remapped to page N in another segment */
     assert(((addr - stm_object_pages) / 4096UL - pgoff) % NB_PAGES == 0);
 
+#ifdef USE_REMAP_FILE_PAGES
     int res = remap_file_pages(addr, size, 0, pgoff, 0);
     if (UNLIKELY(res < 0))
         stm_fatalerror("remap_file_pages: %m");
+#else
+    char *res = mmap(addr, size,
+                     PROT_READ | PROT_WRITE,
+                     (MAP_PAGES_FLAGS & ~MAP_ANONYMOUS) | MAP_FIXED,
+                     stm_object_pages_fd, pgoff * 4096UL);
+    if (UNLIKELY(res != addr))
+        stm_fatalerror("mmap (remapping page): %m");
+#endif
 }
 
 static void pages_initialize_shared(uintptr_t pagenum, uintptr_t count)
@@ -108,18 +117,20 @@
 {
     /* check this thread's 'pages_privatized' bit */
     uint64_t bitmask = 1UL << (STM_SEGMENT->segment_num - 1);
-    struct page_shared_s *ps = &pages_privatized[pagenum - PAGE_FLAG_START];
+    volatile struct page_shared_s *ps = (volatile struct page_shared_s *)
+        &pages_privatized[pagenum - PAGE_FLAG_START];
     if (ps->by_segment & bitmask) {
         /* the page is already privatized; nothing to do */
         return;
     }
 
-#ifndef NDEBUG
-    spinlock_acquire(lock_pages_privatizing[STM_SEGMENT->segment_num]);
-#endif
+    long i;
+    for (i = 1; i <= NB_SEGMENTS; i++) {
+        spinlock_acquire(get_priv_segment(i)->privatization_lock);
+    }
 
     /* add this thread's 'pages_privatized' bit */
-    __sync_fetch_and_add(&ps->by_segment, bitmask);
+    ps->by_segment |= bitmask;
 
     /* "unmaps" the page to make the address space location correspond
        again to its underlying file offset (XXX later we should again
@@ -133,9 +144,9 @@
     /* copy the content from the shared (segment 0) source */
     pagecopy(new_page, stm_object_pages + pagenum * 4096UL);
 
-#ifndef NDEBUG
-    spinlock_release(lock_pages_privatizing[STM_SEGMENT->segment_num]);
-#endif
+    for (i = NB_SEGMENTS; i >= 1; i--) {
+        spinlock_release(get_priv_segment(i)->privatization_lock);
+    }
 }
 
 static void _page_do_reshare(long segnum, uintptr_t pagenum)
@@ -167,6 +178,7 @@
 
 static void pages_setup_readmarkers_for_nursery(void)
 {
+#ifdef USE_REMAP_FILE_PAGES
     /* The nursery page's read markers are never read, but must still
        be writeable.  We'd like to map the pages to a general "trash
        page"; missing one, we remap all the pages over to the same one.
@@ -185,4 +197,5 @@
             /* errors here ignored */
         }
     }
+#endif
 }
diff --git a/c7/stm/pages.h b/c7/stm/pages.h
--- a/c7/stm/pages.h
+++ b/c7/stm/pages.h
@@ -19,6 +19,8 @@
 #define PAGE_FLAG_START   END_NURSERY_PAGE
 #define PAGE_FLAG_END     NB_PAGES
 
+#define USE_REMAP_FILE_PAGES
+
 struct page_shared_s {
 #if NB_SEGMENTS <= 8
     uint8_t by_segment;
@@ -34,20 +36,6 @@
 };
 
 static struct page_shared_s pages_privatized[PAGE_FLAG_END - PAGE_FLAG_START];
-/* Rules for concurrent access to this array, possibly with is_private_page():
-
-   - we clear bits only during major collection, when all threads are
-     synchronized anyway
-
-   - we set only the bit corresponding to our segment number, using
-     an atomic addition; and we do it _before_ we actually make the
-     page private.
-
-   - concurrently, other threads checking the bits might (rarely)
-     get the answer 'true' to is_private_page() even though it is not
-     actually private yet.  This inconsistency is in the direction
-     that we want for synchronize_object_now().
-*/
 
 static void pages_initialize_shared(uintptr_t pagenum, uintptr_t count);
 static void page_privatize(uintptr_t pagenum);
@@ -86,7 +74,3 @@
     if (pages_privatized[pagenum - PAGE_FLAG_START].by_segment != 0)
         page_reshare(pagenum);
 }
-
-#ifndef NDEBUG
-static char lock_pages_privatizing[NB_SEGMENTS + 1] = { 0 };
-#endif
diff --git a/c7/stm/setup.c b/c7/stm/setup.c
--- a/c7/stm/setup.c
+++ b/c7/stm/setup.c
@@ -3,7 +3,8 @@
 #endif
 
 
-static char *setup_mmap(char *reason)
+#ifdef USE_REMAP_FILE_PAGES
+static char *setup_mmap(char *reason, int *ignored)
 {
     char *result = mmap(NULL, TOTAL_MEMORY,
                         PROT_READ | PROT_WRITE,
@@ -13,6 +14,45 @@
 
     return result;
 }
+static void close_fd_mmap(int ignored)
+{
+}
+#else
+#include <fcntl.h>           /* For O_* constants */
+static char *setup_mmap(char *reason, int *map_fd)
+{
+    char name[128];
+    sprintf(name, "/stmgc-c7-bigmem-%ld-%.18e",
+            (long)getpid(), get_stm_time());
+
+    /* Create the big shared memory object, and immediately unlink it.
+       There is a small window where if this process is killed the
+       object is left around.  It doesn't seem possible to do anything
+       about it...
+    */
+    int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+    shm_unlink(name);
+
+    if (fd == -1) {
+        stm_fatalerror("%s failed (stm_open): %m", reason);
+    }
+    if (ftruncate(fd, TOTAL_MEMORY) != 0) {
+        stm_fatalerror("%s failed (ftruncate): %m", reason);
+    }
+    char *result = mmap(NULL, TOTAL_MEMORY,
+                        PROT_READ | PROT_WRITE,
+                        MAP_PAGES_FLAGS & ~MAP_ANONYMOUS, fd, 0);
+    if (result == MAP_FAILED) {
+        stm_fatalerror("%s failed (mmap): %m", reason);
+    }
+    *map_fd = fd;
+    return result;
+}
+static void close_fd_mmap(int map_fd)
+{
+    close(map_fd);
+}
+#endif
 
 static void setup_protection_settings(void)
 {
@@ -56,7 +96,8 @@
            (FIRST_READMARKER_PAGE * 4096UL));
     assert(_STM_FAST_ALLOC <= NB_NURSERY_PAGES * 4096);
 
-    stm_object_pages = setup_mmap("initial stm_object_pages mmap()");
+    stm_object_pages = setup_mmap("initial stm_object_pages mmap()",
+                                  &stm_object_pages_fd);
     setup_protection_settings();
 
     long i;
@@ -78,6 +119,7 @@
         pr->objects_pointing_to_nursery = NULL;
         pr->large_overflow_objects = NULL;
         pr->modified_old_objects = list_create();
+        pr->modified_old_objects_markers = list_create();
         pr->young_weakrefs = list_create();
         pr->old_weakrefs = list_create();
         pr->young_outside_nursery = tree_create();
@@ -85,15 +127,16 @@
         pr->callbacks_on_abort = tree_create();
         pr->overflow_number = GCFLAG_OVERFLOW_NUMBER_bit0 * i;
         highest_overflow_number = pr->overflow_number;
+        pr->pub.transaction_read_version = 0xff;
     }
 
     /* The pages are shared lazily, as remap_file_pages() takes a relatively
        long time for each page.
 
-       The read markers are initially zero, which is correct:
-       STM_SEGMENT->transaction_read_version never contains zero,
-       so a null read marker means "not read" whatever the
-       current transaction_read_version is.
+       The read markers are initially zero, but we set anyway
+       transaction_read_version to 0xff in order to force the first
+       transaction to "clear" the read markers by mapping a different,
+       private range of addresses.
     */
 
     setup_sync();
@@ -115,6 +158,7 @@
         assert(pr->objects_pointing_to_nursery == NULL);
         assert(pr->large_overflow_objects == NULL);
         list_free(pr->modified_old_objects);
+        list_free(pr->modified_old_objects_markers);
         list_free(pr->young_weakrefs);
         list_free(pr->old_weakrefs);
         tree_free(pr->young_outside_nursery);
@@ -124,6 +168,7 @@
 
     munmap(stm_object_pages, TOTAL_MEMORY);
     stm_object_pages = NULL;
+    close_fd_mmap(stm_object_pages_fd);
 
     teardown_core();
     teardown_sync();
@@ -154,11 +199,13 @@
     struct stm_shadowentry_s *s = (struct stm_shadowentry_s *)start;
     tl->shadowstack = s;
     tl->shadowstack_base = s;
+    STM_PUSH_ROOT(*tl, STM_STACK_MARKER_OLD);
 }
 
 static void _done_shadow_stack(stm_thread_local_t *tl)
 {
-    assert(tl->shadowstack >= tl->shadowstack_base);
+    assert(tl->shadowstack > tl->shadowstack_base);
+    assert(tl->shadowstack_base->ss == (object_t *)STM_STACK_MARKER_OLD);
 
     char *start = (char *)tl->shadowstack_base;
     _shadowstack_trap_page(start, PROT_READ | PROT_WRITE);
diff --git a/c7/stm/setup.h b/c7/stm/setup.h
new file mode 100644
--- /dev/null
+++ b/c7/stm/setup.h
@@ -0,0 +1,5 @@
+
+static char *setup_mmap(char *reason, int *map_fd);
+static void close_fd_mmap(int map_fd);
+static void setup_protection_settings(void);
+static pthread_t *_get_cpth(stm_thread_local_t *);
diff --git a/c7/stm/sync.c b/c7/stm/sync.c
--- a/c7/stm/sync.c
+++ b/c7/stm/sync.c
@@ -2,6 +2,10 @@
 #include <sys/prctl.h>
 #include <asm/prctl.h>
 
+#ifndef _STM_CORE_H_
+# error "must be compiled via stmgc.c"
+#endif
+
 
 /* Each segment can be in one of three possible states, described by
    the segment variable 'safe_point':
@@ -260,6 +264,18 @@
 static bool _safe_points_requested = false;
 #endif
 
+static void signal_other_to_commit_soon(struct stm_priv_segment_info_s *other_pseg)
+{
+    assert(_has_mutex());
+    /* never overwrite abort signals or safepoint requests
+       (too messy to deal with) */
+    if (!other_pseg->signalled_to_commit_soon
+        && !is_abort(other_pseg->pub.nursery_end)
+        && !pause_signalled) {
+        other_pseg->pub.nursery_end = NSE_SIGCOMMITSOON;
+    }
+}
+
 static void signal_everybody_to_pause_running(void)
 {
     assert(_safe_points_requested == false);
@@ -323,7 +339,21 @@
         if (STM_SEGMENT->nursery_end == NURSERY_END)
             break;    /* no safe point requested */
 
+        if (STM_SEGMENT->nursery_end == NSE_SIGCOMMITSOON) {
+            if (previous_state == -1) {
+                previous_state = change_timing_state(STM_TIME_SYNC_COMMIT_SOON);
+            }
+
+            STM_PSEGMENT->signalled_to_commit_soon = true;
+            stmcb_commit_soon();
+            if (!pause_signalled) {
+                STM_SEGMENT->nursery_end = NURSERY_END;
+                break;
+            }
+            STM_SEGMENT->nursery_end = NSE_SIGPAUSE;
+        }
         assert(STM_SEGMENT->nursery_end == NSE_SIGPAUSE);
+        assert(pause_signalled);
 
         /* If we are requested to enter a safe-point, we cannot proceed now.
            Wait until the safe-point request is removed for us. */
diff --git a/c7/stm/timing.c b/c7/stm/timing.c
--- a/c7/stm/timing.c
+++ b/c7/stm/timing.c
@@ -25,18 +25,26 @@
     return oldstate;
 }
 
-static void change_timing_state_tl(stm_thread_local_t *tl,
-                                   enum stm_time_e newstate)
+static double change_timing_state_tl(stm_thread_local_t *tl,
+                                     enum stm_time_e newstate)
 {
     TIMING_CHANGE(tl, newstate);
+    return elasped;
 }
 
 static void timing_end_transaction(enum stm_time_e attribute_to)
 {
     stm_thread_local_t *tl = STM_SEGMENT->running_thread;
     TIMING_CHANGE(tl, STM_TIME_OUTSIDE_TRANSACTION);
-    add_timing(tl, attribute_to, tl->timing[STM_TIME_RUN_CURRENT]);
+    double time_this_transaction = tl->timing[STM_TIME_RUN_CURRENT];
+    add_timing(tl, attribute_to, time_this_transaction);
     tl->timing[STM_TIME_RUN_CURRENT] = 0.0f;
+
+    if (attribute_to != STM_TIME_RUN_COMMITTED) {
+        struct stm_priv_segment_info_s *pseg =
+            get_priv_segment(STM_SEGMENT->segment_num);
+        marker_copy(tl, pseg, attribute_to, time_this_transaction);
+    }
 }
 
 static const char *timer_names[] = {
@@ -51,6 +59,7 @@
     "wait write read",
     "wait inevitable",
     "wait other",
+    "sync commit soon",
     "bookkeeping",
     "minor gc",
     "major gc",
@@ -70,9 +79,13 @@
         s_mutex_lock();
         fprintf(stderr, "thread %p:\n", tl);
         for (i = 0; i < _STM_TIME_N; i++) {
-            fprintf(stderr, "    %-24s %9u  %.3f s\n",
+            fprintf(stderr, "    %-24s %9u %8.3f s\n",
                     timer_names[i], tl->events[i], (double)tl->timing[i]);
         }
+        fprintf(stderr, "    %-24s %6s %11.6f s\n",
+                "longest recorded marker", "", tl->longest_marker_time);
+        fprintf(stderr, "    \"%.*s\"\n",
+                (int)_STM_MARKER_LEN, tl->longest_marker_self);
         s_mutex_unlock();
     }
 }
diff --git a/c7/stm/timing.h b/c7/stm/timing.h
--- a/c7/stm/timing.h
+++ b/c7/stm/timing.h
@@ -8,7 +8,7 @@
 }
 
 static enum stm_time_e change_timing_state(enum stm_time_e newstate);
-static void change_timing_state_tl(stm_thread_local_t *tl,
-                                   enum stm_time_e newstate);
+static double change_timing_state_tl(stm_thread_local_t *tl,
+                                     enum stm_time_e newstate);
 
 static void timing_end_transaction(enum stm_time_e attribute_to);
diff --git a/c7/stmgc.c b/c7/stmgc.c
--- a/c7/stmgc.c
+++ b/c7/stmgc.c
@@ -8,6 +8,7 @@
 #include "stm/pages.h"
 #include "stm/gcpage.h"
 #include "stm/sync.h"
+#include "stm/setup.h"
 #include "stm/largemalloc.h"
 #include "stm/nursery.h"
 #include "stm/contention.h"
@@ -15,6 +16,7 @@
 #include "stm/fprintcolor.h"
 #include "stm/weakref.h"
 #include "stm/timing.h"
+#include "stm/marker.h"
 
 #include "stm/misc.c"
 #include "stm/list.c"
@@ -35,3 +37,4 @@
 #include "stm/fprintcolor.c"
 #include "stm/weakref.c"
 #include "stm/timing.c"
+#include "stm/marker.c"
diff --git a/c7/stmgc.h b/c7/stmgc.h
--- a/c7/stmgc.h
+++ b/c7/stmgc.h
@@ -66,6 +66,7 @@
     STM_TIME_WAIT_WRITE_READ,
     STM_TIME_WAIT_INEVITABLE,
     STM_TIME_WAIT_OTHER,
+    STM_TIME_SYNC_COMMIT_SOON,
     STM_TIME_BOOKKEEPING,
     STM_TIME_MINOR_GC,
     STM_TIME_MAJOR_GC,
@@ -73,6 +74,8 @@
     _STM_TIME_N
 };
 
+#define _STM_MARKER_LEN  80
+
 typedef struct stm_thread_local_s {
     /* every thread should handle the shadow stack itself */
     struct stm_shadowentry_s *shadowstack, *shadowstack_base;
@@ -90,6 +93,11 @@
     float timing[_STM_TIME_N];
     double _timing_cur_start;
     enum stm_time_e _timing_cur_state;
+    /* the marker with the longest associated time so far */
+    enum stm_time_e longest_marker_state;
+    double longest_marker_time;
+    char longest_marker_self[_STM_MARKER_LEN];
+    char longest_marker_other[_STM_MARKER_LEN];
     /* the next fields are handled internally by the library */
     int associated_segment_num;
     struct stm_thread_local_s *prev, *next;
@@ -213,9 +221,13 @@
    The "size rounded up" must be a multiple of 8 and at least 16.
    "Tracing" an object means enumerating all GC references in it,
    by invoking the callback passed as argument.
+   stmcb_commit_soon() is called when it is advised to commit
+   the transaction as soon as possible in order to avoid conflicts
+   or improve performance in general.
 */
 extern ssize_t stmcb_size_rounded_up(struct object_s *);
 extern void stmcb_trace(struct object_s *, void (object_t **));
+extern void stmcb_commit_soon(void);
 
 
 /* Allocate an object of the given size, which must be a multiple
@@ -268,6 +280,8 @@
 #define STM_PUSH_ROOT(tl, p)   ((tl).shadowstack++->ss = (object_t *)(p))
 #define STM_POP_ROOT(tl, p)    ((p) = (typeof(p))((--(tl).shadowstack)->ss))
 #define STM_POP_ROOT_RET(tl)   ((--(tl).shadowstack)->ss)
+#define STM_STACK_MARKER_NEW  (-41)
+#define STM_STACK_MARKER_OLD  (-43)
 
 
 /* Every thread needs to have a corresponding stm_thread_local_t
@@ -370,6 +384,43 @@
 void stm_flush_timing(stm_thread_local_t *tl, int verbose);
 
 
+/* The markers pushed in the shadowstack are an odd number followed by a
+   regular pointer.  When needed, this library invokes this callback to
+   turn this pair into a human-readable explanation. */
+extern void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                                   object_t *following_object,
+                                   char *outputbuf, size_t outputbufsize);
+extern void (*stmcb_debug_print)(const char *cause, double time,
+                                 const char *marker);
+
+/* Conventience macros to push the markers into the shadowstack */
+#define STM_PUSH_MARKER(tl, odd_num, p)   do {  \
+    uintptr_t _odd_num = (odd_num);             \
+    assert(_odd_num & 1);                       \
+    STM_PUSH_ROOT(tl, _odd_num);                \
+    STM_PUSH_ROOT(tl, p);                       \
+} while (0)
+
+#define STM_POP_MARKER(tl)   ({                 \
+    object_t *_popped = STM_POP_ROOT_RET(tl);   \
+    STM_POP_ROOT_RET(tl);                       \
+    _popped;                                    \
+})
+
+#define STM_UPDATE_MARKER_NUM(tl, odd_num)  do {                \
+    uintptr_t _odd_num = (odd_num);                             \
+    assert(_odd_num & 1);                                       \
+    struct stm_shadowentry_s *_ss = (tl).shadowstack - 2;       \
+    while (!(((uintptr_t)(_ss->ss)) & 1)) {                     \
+        _ss--;                                                  \
+        assert(_ss >= (tl).shadowstack_base);                   \
+    }                                                           \
+    _ss->ss = (object_t *)_odd_num;                             \
+} while (0)
+
+char *_stm_expand_marker(void);
+
+
 /* ==================== END ==================== */
 
 #endif
diff --git a/c7/test/support.py b/c7/test/support.py
--- a/c7/test/support.py
+++ b/c7/test/support.py
@@ -12,6 +12,8 @@
 #define STM_NB_SEGMENTS ...
 #define _STM_FAST_ALLOC ...
 #define _STM_GCFLAG_WRITE_BARRIER ...
+#define STM_STACK_MARKER_NEW ...
+#define STM_STACK_MARKER_OLD ...
 
 struct stm_shadowentry_s {
     object_t *ss;
@@ -26,6 +28,10 @@
     int associated_segment_num;
     uint32_t events[];
     float timing[];
+    int longest_marker_state;
+    double longest_marker_time;
+    char longest_marker_self[];
+    char longest_marker_other[];
     ...;
 } stm_thread_local_t;
 
@@ -121,6 +127,17 @@
 #define STM_TIME_SYNC_PAUSE ...
 
 void stm_flush_timing(stm_thread_local_t *, int);
+
+void (*stmcb_expand_marker)(char *segment_base, uintptr_t odd_number,
+                            object_t *following_object,
+                            char *outputbuf, size_t outputbufsize);
+void (*stmcb_debug_print)(const char *cause, double time,
+                          const char *marker);
+
+void stm_push_marker(stm_thread_local_t *, uintptr_t, object_t *);
+void stm_update_marker_num(stm_thread_local_t *, uintptr_t);
+void stm_pop_marker(stm_thread_local_t *);
+char *_stm_expand_marker(void);
 """)
 
 
@@ -275,6 +292,24 @@
     }
 }
 
+void stm_push_marker(stm_thread_local_t *tl, uintptr_t onum, object_t *ob)
+{
+    STM_PUSH_MARKER(*tl, onum, ob);
+}
+
+void stm_update_marker_num(stm_thread_local_t *tl, uintptr_t onum)
+{
+    STM_UPDATE_MARKER_NUM(*tl, onum);
+}
+
+void stm_pop_marker(stm_thread_local_t *tl)
+{
+    STM_POP_MARKER(*tl);
+}
+
+void stmcb_commit_soon()
+{
+}
 ''', sources=source_files,
      define_macros=[('STM_TESTS', '1'),
                     ('STM_LARGEMALLOC_TEST', '1'),
@@ -446,6 +481,8 @@
         self.current_thread = 0
 
     def teardown_method(self, meth):
+        lib.stmcb_expand_marker = ffi.NULL
+        lib.stmcb_debug_print = ffi.NULL
         tl = self.tls[self.current_thread]
         if lib._stm_in_transaction(tl) and lib.stm_is_inevitable():
             self.commit_transaction()      # must succeed!
@@ -517,7 +554,8 @@
     def pop_root(self):
         tl = self.tls[self.current_thread]
         curlength = tl.shadowstack - tl.shadowstack_base
-        if curlength == 0:
+        assert curlength >= 1
+        if curlength == 1:
             raise EmptyStack
         assert 0 < curlength <= SHADOWSTACK_LENGTH
         tl.shadowstack -= 1
diff --git a/c7/test/test_gcpage.py b/c7/test/test_gcpage.py
--- a/c7/test/test_gcpage.py
+++ b/c7/test/test_gcpage.py
@@ -228,3 +228,22 @@
 
         self.start_transaction()
         assert stm_get_char(self.get_thread_local_obj()) == 'L'
+
+    def test_marker_1(self):
+        self.start_transaction()
+        p1 = stm_allocate(600)
+        stm_set_char(p1, 'o')
+        self.push_root(p1)
+        self.push_root(ffi.cast("object_t *", lib.STM_STACK_MARKER_NEW))
+        p2 = stm_allocate(600)
+        stm_set_char(p2, 't')
+        self.push_root(p2)
+        stm_major_collect()
+        assert lib._stm_total_allocated() == 2 * 616
+        #
+        p2 = self.pop_root()
+        m = self.pop_root()
+        assert m == ffi.cast("object_t *", lib.STM_STACK_MARKER_OLD)
+        p1 = self.pop_root()
+        assert stm_get_char(p1) == 'o'
+        assert stm_get_char(p2) == 't'
diff --git a/c7/test/test_marker.py b/c7/test/test_marker.py
new file mode 100644
--- /dev/null
+++ b/c7/test/test_marker.py
@@ -0,0 +1,340 @@
+from support import *
+import py, time
+
+class TestMarker(BaseTest):
+
+    def test_marker_odd_simple(self):
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 29))
+        stm_minor_collect()
+        stm_major_collect()
+        # assert did not crash
+        x = self.pop_root()
+        assert int(ffi.cast("uintptr_t", x)) == 29
+
+    def test_abort_marker_no_shadowstack(self):
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_OUTSIDE_TRANSACTION
+        assert tl.longest_marker_time == 0.0
+        #
+        self.start_transaction()
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert tl.longest_marker_self[0] == '\x00'
+        assert tl.longest_marker_other[0] == '\x00'
+
+    def test_abort_marker_shadowstack(self):
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert tl.longest_marker_self[0] == '\x00'
+        assert tl.longest_marker_other[0] == '\x00'
+
+    def test_abort_marker_no_shadowstack_cb(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            seen.append(1)
+        lib.stmcb_expand_marker = expand_marker
+        seen = []
+        #
+        self.start_transaction()
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_self[0] == '\x00'
+        assert not seen
+
+    def test_abort_marker_shadowstack_cb(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d %r\x00' % (number, ptr)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        start = time.time()
+        while abs(time.time() - start) <= 0.1:
+            pass
+        self.abort_transaction()
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_OTHER
+        assert 0.099 <= tl.longest_marker_time <= 0.9
+        assert ffi.string(tl.longest_marker_self) == '29 %r' % (p,)
+        assert ffi.string(tl.longest_marker_other) == ''
+
+    def test_macros(self):
+        self.start_transaction()
+        p = stm_allocate(16)
+        tl = self.get_stm_thread_local()
+        lib.stm_push_marker(tl, 29, p)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 29)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        lib.stm_update_marker_num(tl, 27)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 27)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        self.push_root(p)
+        lib.stm_update_marker_num(tl, 27)
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == p
+        p1 = self.pop_root()
+        assert p1 == ffi.cast("object_t *", 27)
+        py.test.raises(EmptyStack, self.pop_root)
+        #
+        lib.stm_push_marker(tl, 29, p)
+        lib.stm_pop_marker(tl)
+        py.test.raises(EmptyStack, self.pop_root)
+
+    def test_stm_expand_marker(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d %r\x00' % (number, ptr)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        self.push_root(stm_allocate(32))
+        self.push_root(stm_allocate(16))
+        raw = lib._stm_expand_marker()
+        assert ffi.string(raw) == '29 %r' % (p,)
+
+    def test_stmcb_debug_print(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '<<<%d>>>\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        @ffi.callback("void(char *, double, char *)")
+        def debug_print(cause, time, marker):
+            if 0.0 < time < 1.0:
+                time = "time_ok"
+            seen.append((ffi.string(cause), time, ffi.string(marker)))
+        seen = []
+        lib.stmcb_expand_marker = expand_marker
+        lib.stmcb_debug_print = debug_print
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(p)
+        self.abort_transaction()
+        #
+        assert seen == [("run aborted other", "time_ok", "<<<29>>>")]
+
+    def test_multiple_markers(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            seen.append(number)
+            s = '%d %r\x00' % (number, ptr == ffi.NULL)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        seen = []
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        self.push_root(ffi.cast("object_t *", 27))
+        self.push_root(p)
+        self.push_root(ffi.cast("object_t *", 29))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        raw = lib._stm_expand_marker()
+        assert ffi.string(raw) == '29 True'
+        assert seen == [29]
+
+    def test_double_abort_markers_cb_write_write(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        p = stm_allocate_old(16)
+        #
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_set_char(p, 'A')
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_minor_collect()
+        #
+        self.switch(1)
+        self.start_transaction()
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        py.test.raises(Conflict, stm_set_char, p, 'B')
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_WRITE_WRITE
+        assert ffi.string(tl.longest_marker_self) == '21'
+        assert ffi.string(tl.longest_marker_other) == '19'
+
+    def test_double_abort_markers_cb_inevitable(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            c = (base + int(ffi.cast("uintptr_t", ptr)))[8]
+            s = '%d %r\x00' % (number, c)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker
+        #
+        self.start_transaction()
+        p = stm_allocate(16)
+        stm_set_char(p, 'A')
+        self.push_root(ffi.cast("object_t *", 19))
+        self.push_root(ffi.cast("object_t *", p))
+        self.become_inevitable()
+        self.pop_root()
+        self.pop_root()
+        self.push_root(ffi.cast("object_t *", 17))
+        self.push_root(ffi.cast("object_t *", ffi.NULL))
+        stm_minor_collect()
+        #
+        self.switch(1)
+        self.start_transaction()
+        p = stm_allocate(16)
+        stm_set_char(p, 'B')
+        self.push_root(ffi.cast("object_t *", 21))
+        self.push_root(ffi.cast("object_t *", p))
+        py.test.raises(Conflict, self.become_inevitable)
+        #
+        tl = self.get_stm_thread_local()
+        assert tl.longest_marker_state == lib.STM_TIME_RUN_ABORTED_INEVITABLE
+        assert ffi.string(tl.longest_marker_self) == "21 'B'"
+        assert ffi.string(tl.longest_marker_other) == "19 'A'"
+
+    def test_read_write_contention(self):
+        @ffi.callback("void(char *, uintptr_t, object_t *, char *, size_t)")
+        def expand_marker(base, number, ptr, outbuf, outbufsize):
+            s = '%d\x00' % (number,)
+            assert len(s) <= outbufsize
+            outbuf[0:len(s)] = s
+        lib.stmcb_expand_marker = expand_marker


More information about the pypy-commit mailing list