Browse Source

Merge pull request #2889 from enkore/f/mt-1b

multithreading: item.to_optr(), Item.from_optr()
enkore 7 years ago
parent
commit
bccff3413b
4 changed files with 73 additions and 2 deletions
  1. 41 0
      src/borg/_item.c
  2. 1 1
      src/borg/helpers.py
  3. 26 1
      src/borg/item.pyx
  4. 5 0
      src/borg/testsuite/item.py

+ 41 - 0
src/borg/_item.c

@@ -0,0 +1,41 @@
+#include "Python.h"
+
+/*
+ * This is not quite as dark magic as it looks. We just convert the address of (pointer to)
+ * a PyObject into a bytes object in _wrap_object, and convert these bytes back to the
+ * pointer to the original object.
+ *
+ * This mainly looks a bit confusing due to our mental special-casing of "char*" from other
+ * pointers.
+ *
+ * The big upside to this is that this neither does *any* serialization (beyond creating tiny
+ * bytes objects as "stand-ins"), nor has to copy the entire object that's passed around.
+ */
+
+static PyObject *
+_object_to_optr(PyObject *obj)
+{
+    /*
+     * Create a temporary reference to the object being passed around so it does not vanish.
+     * Note that we never decref this one in _unwrap_object, since we just transfer that reference
+     * there, i.e. there is an elided "Py_INCREF(x); Py_DECREF(x)".
+     * Since the reference is transferred, calls to _wrap_object and _unwrap_object must be symmetric.
+     */
+    Py_INCREF(obj);
+    return PyBytes_FromStringAndSize((const char*) &obj, sizeof(void*));
+}
+
+static PyObject *
+_optr_to_object(PyObject *bytes)
+{
+    if(!PyBytes_Check(bytes)) {
+        PyErr_SetString(PyExc_TypeError, "Cannot unwrap non-bytes object");
+        return NULL;
+    }
+    if(PyBytes_Size(bytes) != sizeof(void*)) {
+        PyErr_SetString(PyExc_TypeError, "Invalid length of bytes object");
+        return NULL;
+    }
+    PyObject *object = * (PyObject **) PyBytes_AsString(bytes);
+    return object;
+}

+ 1 - 1
src/borg/helpers.py

@@ -141,7 +141,7 @@ def check_extension_modules():
         raise ExtensionModuleError
     if platform.API_VERSION != platform.OS_API_VERSION != '1.1_01':
         raise ExtensionModuleError
-    if item.API_VERSION != '1.1_02':
+    if item.API_VERSION != '1.1_03':
         raise ExtensionModuleError
 
 

+ 26 - 1
src/borg/item.pyx

@@ -6,7 +6,12 @@ from .helpers import safe_encode, safe_decode
 from .helpers import bigint_to_int, int_to_bigint
 from .helpers import StableDict
 
-API_VERSION = '1.1_02'
+cdef extern from "_item.c":
+    object _object_to_optr(object obj)
+    object _optr_to_object(object bytes)
+
+
+API_VERSION = '1.1_03'
 
 
 class PropDict:
@@ -227,6 +232,26 @@ class Item(PropDict):
                 setattr(self, attr, size)
         return size
 
+    def to_optr(self):
+        """
+        Return an "object pointer" (optr), an opaque bag of bytes.
+        The return value is effectively a reference to this object
+        that can be passed exactly once to Item.from_optr to get this
+        object back.
+
+        to_optr/from_optr must be used symmetrically,
+        don't call from_optr multiple times.
+
+        This object can't be deallocated after a call to to_optr()
+        until from_optr() is called.
+        """
+        return _object_to_optr(self)
+
+    @classmethod
+    def from_optr(self, optr):
+        return _optr_to_object(optr)
+
+
 
 class EncryptedKey(PropDict):
     """

+ 5 - 0
src/borg/testsuite/item.py

@@ -164,3 +164,8 @@ def test_item_file_size():
 def test_item_file_size_no_chunks():
     item = Item(mode=0o100666)
     assert item.get_size() == 0
+
+
+def test_item_optr():
+    item = Item()
+    assert Item.from_optr(item.to_optr()) is item