Author: Armin Rigo <[email protected]>
Branch: extradoc
Changeset: r4706:845d94e938c7
Date: 2012-08-19 10:37 +0200
http://bitbucket.org/pypy/extradoc/changeset/845d94e938c7/

Log:    Conceptual simplification of the commit model.

        This brings it closer to what we have in code now, and avoids the
        need to re-update the local objects every time CMPXCHG fails.

diff --git a/talk/stm2012/stmimpl.rst b/talk/stm2012/stmimpl.rst
--- a/talk/stm2012/stmimpl.rst
+++ b/talk/stm2012/stmimpl.rst
@@ -397,24 +397,24 @@
 Committing
 ------------------------------------
 
-Committing is a four-steps process:
+Committing is a five-steps process:
 
-1. We first find all global objects that we have written to, and mark
-them "locked" by putting in their ``h_revision`` field a special value
-that will cause parallel CPUs to spin loop in ``LatestGlobalRevision``.
-We also prepare the local versions of these objects to become the next
-head of the chained lists, by fixing the headers.
+1. We first find all global objects with a local copy that has been
+written to, and mark them "locked" by putting in their ``h_revision``
+field a special value that will cause parallel CPUs to spin loop in
+``LatestGlobalRevision``.
 
-2. We atomically increase the global time (with LOCK CMPXCHG).  This
-causes a MFENCE too: all prepared local objects are visible to all other
-CPUs afterwards.
+2. We atomically increase the global time (with LOCK CMPXCHG).
 
-3. We check again that all read objects are still up-to-date, i.e. have
+3. We prepare the local versions of the global modified objects to
+become the next head of the chained lists, by fixing the headers.
+
+4. We check again that all read objects are still up-to-date, i.e. have
 not been replaced by a revision more recent than ``start_time``.  (This
 is the last chance to abort a conflicting transaction; if we do, we have
 to remember to release the locks.)
 
-4. Finally, we unlock the global objects by overriding their
+5. Finally, we unlock the global objects by overriding their
 ``h_revision``.  We put there now a pointer to the corresponding
 previously-local object.  The previously-local object plays from now on
 the role of the global head of the chained list.
@@ -422,42 +422,33 @@
 In pseudo-code::
 
     def CommitTransaction():
+        AcquireLocks()
         cur_time = global_cur_time
-        AcquireLocks(cur_time)
         while not CMPXCHG(&global_cur_time, cur_time, cur_time + 2):
             cur_time = global_cur_time    # try again
-            AcquireLocksAgain(cur_time)
+        FixHeadersOfLocalModified(cur_time)
         ValidateDuringCommit()
         UpdateChainHeads()
 
 Note the general style of usage of CMPXCHG: we first read normally the
-current version of some data (here ``global_cur_time``), do some
-preparations based on this value (here ``AcquireLocks``), and then do
-the expensive CMPXCHG operation.  It checks atomically if the value 
-of the data is still equal to the old value; if yes, it replaces it
-with a new specified value and returns True; otherwise, it simply
-returns False.  In the latter case we just loop again.
+current version of some data (here ``global_cur_time``), and then do the
+expensive CMPXCHG operation.  It checks atomically if the value of the
+data is still equal to the old value; if yes, it replaces it with a new
+specified value and returns True; otherwise, it simply returns False.
+In the latter case we just loop again.  (A simple case like this could
+also be done with XADD, with a locked increment-by-two.)
 
-Here is ``AcquireLocks``, doing both the locking of the global objects
-and the fixing of the local objects.  This is done together *before* we
-use CMPXCHG, so that after a successful CMPXCHG the other CPUs are
-guaranteed to see the new values --- both the locks and the
-previously-local objects with the proper fixes.
-
-Note that "locking" here only means writing a value >= LOCKED in the
+Here is ``AcquireLocks``, locking the global objects.  Note that
+"locking" here only means writing a value >= LOCKED in the
 ``h_revision`` field; it does not involve OS-specific thread locks::
 
-    def AcquireLocks(cur_time):
-        new_revision = cur_time + 1     # make an odd number
+    def AcquireLocks():
         for (R, L) in global_to_local:
-            L->h_global = True
             if not L->h_written:
+                L->h_global = True
                 #L->h_revision already points to R
                 L->h_possibly_outdated = True
                 continue
-            L->h_revision = new_revision
-            L->h_written = False
-            #L->h_possibly_outdated is already False
             v = R->h_revision
             if not (v & 1):         # "is a pointer", i.e.
                 AbortTransaction()  #   "has a more recent revision"
@@ -471,9 +462,13 @@
 instead, we turn the object into a "global but outdated" object, keeping
 the same ``h_revision`` but with a different meaning.)
 
-We use CMPXCHG to store the lock.  This is required, because we must
-not conflict with another CPU that would try to write the same lock
-in the same field --- in that case, only one CPU can succeed.
+We use CMPXCHG to store the lock.  This is required, because we must not
+conflict with another CPU that would try to write its own lock in the
+same field --- in that case, only one CPU can succeed.  The order of
+enumeration of ``global_to_local`` must be the same one --- for example,
+following the numeric order of ``R``.  This is needed to avoid
+deadlocks.  Alternatively we could consider this case rare, and abort
+instead of waiting.
 
 The lock's value ``my_lock`` is, precisely, a very large odd number, at
 least LOCKED (which should be some value like 0xFFFF0000).  As we can
@@ -481,20 +476,24 @@
 calling ``ValidateDuringTransaction`` over and over again, until the
 lock is released (i.e.  another value is written in ``h_revision``).
 
-``AcquireLocksAgain`` is called instead of ``AcquireLocks`` if the first
-CMPXCHG fails in ``CommitTransaction``.  It just needs to update the
-previously-local object's ``h_revision``, keeping the already-acquired
-locks::
 
-    def AcquireLocksAgain(cur_time):
-        new_revision = cur_time + 1
+After this, ``CommitTransaction`` increases the global time and then
+calls ``FixHeadersOfLocalModified`` to adjust the local object's
+headers::
+
+    def FixHeadersOfLocalModified(cur_time):
+        new_revision = cur_time + 1     # make an odd number
         for (R, L, v) in locks_acquired:
+            L->h_global = True
+            L->h_written = False
+            #L->h_possibly_outdated is already False
             L->h_revision = new_revision
 
 
-In case ``AbortTransaction`` is called, it must release the locks.  This
-is done by writing back the original timestamps in the ``h_revision``
-fields::
+Then we call ``ValidateDuringCommit`` defined above.  It may still
+abort.  In case ``AbortTransaction`` is called, it must release the
+locks.  This is done by writing back the original timestamps in the
+``h_revision`` fields::
 
     def AbortTransaction():
         for R, L, v in locks_acquired:
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to