Author: Armin Rigo <[email protected]>
Branch: queue
Changeset: r1867:2cedc7732a5e
Date: 2015-06-18 20:40 +0200
http://bitbucket.org/pypy/stmgc/changeset/2cedc7732a5e/

Log:    Use a regular lock instead of compare-and-swap for 'old_entries'.
        See comment for why compare-and-swap has subtle issues with linked
        list in the presence of free()

diff --git a/c8/stm/queue.c b/c8/stm/queue.c
--- a/c8/stm/queue.c
+++ b/c8/stm/queue.c
@@ -39,8 +39,10 @@
        and the 'segs' is an array of items 64 bytes each */
     stm_queue_segment_t segs[STM_NB_SEGMENTS];
 
-    /* a chained list of old entries in the queue */
-    queue_entry_t *volatile old_entries;
+    /* a chained list of old entries in the queue; modified only
+       with the lock */
+    queue_entry_t *old_entries;
+    uint8_t old_entries_lock;
 
     /* total of 'unfinished_tasks_in_this_transaction' for all
        committed transactions */
@@ -74,17 +76,6 @@
     for (i = 0; i < STM_NB_SEGMENTS; i++) {
         stm_queue_segment_t *seg = &queue->segs[i];
 
-        /* it is possible that queues_deactivate_all() runs in parallel,
-           but it should not be possible at this point for another thread
-           to change 'active' from false to true.  if it is false, then
-           that's it */
-        if (!seg->active) {
-            assert(!seg->added_in_this_transaction);
-            assert(!seg->added_young_limit);
-            assert(!seg->old_objects_popped);
-            continue;
-        }
-
         struct stm_priv_segment_info_s *pseg = get_priv_segment(i + 1);
         spinlock_acquire(pseg->active_queues_lock);
 
@@ -93,9 +84,14 @@
             bool ok = tree_delete_item(pseg->active_queues, (uintptr_t)queue);
             assert(ok);
             (void)ok;
+            queue_free_entries(seg->added_in_this_transaction);
+            queue_free_entries(seg->old_objects_popped);
         }
-        queue_free_entries(seg->added_in_this_transaction);
-        queue_free_entries(seg->old_objects_popped);
+        else {
+            assert(!seg->added_in_this_transaction);
+            assert(!seg->added_young_limit);
+            assert(!seg->old_objects_popped);
+        }
 
         spinlock_release(pseg->active_queues_lock);
     }
@@ -171,11 +167,13 @@
             while (tail->next != NULL)
                 tail = tail->next;
             dprintf(("items move to old_entries in queue %p\n", queue));
-         retry:
+
+            spinlock_acquire(queue->old_entries_lock);
             old = queue->old_entries;
             tail->next = old;
-            if (!__sync_bool_compare_and_swap(&queue->old_entries, old, head))
-                goto retry;
+            queue->old_entries = head;
+            spinlock_release(queue->old_entries_lock);
+
             added_any_old_entries = true;
         }
 
@@ -221,6 +219,12 @@
     }
 }
 
+static void queue_check_entry(queue_entry_t *entry)
+{
+    assert(entry->object != NULL);
+    assert(((TLPREFIX int *)entry->object)[1] != 0);   /* userdata != 0 */
+}
+
 object_t *stm_queue_get(object_t *qobj, stm_queue_t *queue, double timeout,
                         stm_thread_local_t *tl)
 {
@@ -239,26 +243,38 @@
         seg->added_in_this_transaction = entry->next;
         if (entry == seg->added_young_limit)
             seg->added_young_limit = entry->next;
+        queue_check_entry(entry);
         result = entry->object;
-        assert(result != NULL);
         free(entry);
         return result;
     }
 
  retry:
+    /* can't easily use compare_and_swap here.  The issue is that
+       if we do "compare_and_swap(&old_entry, entry, entry->next)",
+       then we need to read entry->next, but a parallel thread
+       could have grabbed the same entry and already freed it.
+       More subtly, there is also an ABA problem: even if we
+       read the correct entry->next, maybe a parallel thread
+       can free and reuse this entry.  Then the compare_and_swap
+       succeeds, but the value written is outdated nonsense.
+    */
+    spinlock_acquire(queue->old_entries_lock);
     entry = queue->old_entries;
+    if (entry != NULL)
+        queue->old_entries = entry->next;
+    spinlock_release(queue->old_entries_lock);
+
     if (entry != NULL) {
-        if (!__sync_bool_compare_and_swap(&queue->old_entries,
-                                          entry, entry->next))
-            goto retry;
-
         /* successfully popped the old 'entry'.  It remains in the
-           'old_objects_popped' list for now. */
+           'old_objects_popped' list for now.  From now on, this entry
+           "belongs" to this segment and should never be read by
+           another segment. */
+        queue_check_entry(entry);
         entry->next = seg->old_objects_popped;
         seg->old_objects_popped = entry;
 
         queue_activate(queue);
-        assert(entry->object != NULL);
         return entry->object;
     }
     else {
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to