[py-svn] pytest-xdist commit 2853276d3fd9: fixing a (likely) race condition when simultanous pickling/unpickling can leave

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Fri Apr 30 15:56:26 CEST 2010


# HG changeset patch -- Bitbucket.org
# Project pytest-xdist
# URL http://bitbucket.org/hpk42/pytest-xdist/overview
# User holger krekel <holger at merlinux.eu>
# Date 1272635808 -7200
# Node ID 2853276d3fd97472e67417ef167918024309b1a6
# Parent  65ce742e0e1a4cd1c5d364be6e7e378390467d98
fixing a (likely) race condition when simultanous pickling/unpickling can leave
inconsistent memo states.  Rather than adding locks, the fix actually simplifies the code by
keeping the pickling/unpickling memo's in sync directly and more efficiently, also removing
a long outstanding XXX.

--- a/testing/test_mypickle.py
+++ b/testing/test_mypickle.py
@@ -252,10 +252,3 @@ class TestPickleChannelFunctional:
         channel.waitclose(timeout=2)
 
 
-
-def test_explode():
-    from xdist.mypickle import explode
-    assert isinstance(explode({}.values()), list)
-    l = []
-    assert isinstance(explode(l), list)
-    assert explode(l) is l

--- a/xdist/mypickle.py
+++ b/xdist/mypickle.py
@@ -31,13 +31,12 @@ class MyPickler(Pickler):
     """ Pickler with a custom memoize()
         to take care of unique ID creation. 
         See the usage in ImmutablePickler
-        XXX we could probably extend Pickler 
-            and Unpickler classes to directly
-            update the other'S memos. 
     """
-    def __init__(self, file, protocol, uneven):
+    def __init__(self, immo, file, protocol, uneven):
         Pickler.__init__(self, file, protocol)
         self.uneven = uneven
+        self._unpicklememo = immo._unpicklememo
+        self.memo = immo._picklememo
         
     def memoize(self, obj):
         if self.fast:
@@ -47,13 +46,26 @@ class MyPickler(Pickler):
         key = memo_len * 2 + self.uneven
         self.write(self.put(key))
         self.memo[id(obj)] = key, obj
+        key = makekey(key)
+        if key in self._unpicklememo:
+            assert self._unpicklememo[key] is obj
+        dict.__setitem__(self._unpicklememo, key, obj)
 
     #if sys.version_info < (3,0):
     #    def save_string(self, obj, pack=struct.pack):
     #        obj = unicode(obj)
     #        self.save_unicode(obj, pack=pack)
     #    Pickler.dispatch[str] = save_string 
-
+  
+class UnpicklingDict(dict):
+    def __init__(self, picklememo):
+        super(UnpicklingDict, self).__init__()
+        self._picklememo = picklememo
+   
+    def __setitem__(self, key, obj):
+        super(UnpicklingDict, self).__setitem__(key, obj)
+        self._picklememo[id(obj)] = (fromkey(key), obj)
+    
 class ImmutablePickler:
     def __init__(self, uneven, protocol=0):
         """ ImmutablePicklers are instantiated in Pairs. 
@@ -64,7 +76,7 @@ class ImmutablePickler:
             parameter.
         """
         self._picklememo = {}
-        self._unpicklememo = {}
+        self._unpicklememo = UnpicklingDict(self._picklememo)
         self._protocol = protocol
         self.uneven = uneven and 1 or 0
 
@@ -73,18 +85,13 @@ class ImmutablePickler:
         # which be the case e.g. if you want to pickle 
         # from a forked process back to the original 
         f = py.io.BytesIO()
-        pickler = MyPickler(f, self._protocol, uneven=self.uneven)
-        pickler.memo = self._picklememo
+        pickler = MyPickler(self, f, self._protocol, uneven=self.uneven)
         pickler.memoize(obj)
-        self._updateunpicklememo()
 
     def dumps(self, obj):
         f = py.io.BytesIO()
-        pickler = MyPickler(f, self._protocol, uneven=self.uneven)
-        pickler.memo = self._picklememo
+        pickler = MyPickler(self, f, self._protocol, uneven=self.uneven)
         pickler.dump(obj)
-        if obj is not None:
-            self._updateunpicklememo()
         #print >>debug, "dumped", obj 
         #print >>debug, "picklememo", self._picklememo
         return f.getvalue()
@@ -94,30 +101,10 @@ class ImmutablePickler:
         unpickler = Unpickler(f)
         unpickler.memo = self._unpicklememo
         res = unpickler.load()
-        self._updatepicklememo()
         #print >>debug, "loaded", res
         #print >>debug, "unpicklememo", self._unpicklememo
         return res
 
-    def _updatepicklememo(self):
-        for x, obj in explode(self._unpicklememo.items()):
-            self._picklememo[id(obj)] = (fromkey(x), obj)
-
-    def _updateunpicklememo(self):
-        for key,obj in explode(self._picklememo.values()):
-            key = makekey(key) 
-            if key in self._unpicklememo:
-                assert self._unpicklememo[key] is obj
-            self._unpicklememo[key] = obj
-
-def explode(obj):
-    try:
-        obj[0]
-    except TypeError:
-        return list(obj)
-    except IndexError:
-        pass
-    return obj
 
 NO_ENDMARKER_WANTED = object()



More information about the pytest-commit mailing list