@@ -47,6 +47,9 @@ cdef class Pool:
4747
4848 Thread-safety:
4949 All public methods in this class can be safely called from multiple threads.
50+ Testing thread-safety of this class can be done by checking out the repository
51+ at https://github.com/lysnikolaou/test-cymem-threadsafety and following the
52+ instructions on the README.
5053 """
5154
5255 def __cinit__ (self , PyMalloc pymalloc = Default_Malloc,
@@ -83,12 +86,14 @@ cdef class Pool:
8386 raise MemoryError (" Error assigning %d bytes" % (number * elem_size))
8487 memset(p, 0 , number * elem_size)
8588
86- # We need a critical section on both self and self.addresses here.
87- # If we were just to acquire a crtitical section on self, mutating
88- # self.addresses would implicitly acquire a critical section on
89- # self.addresses, which would release our critical section on self,
90- # potentially allowing another thread to interleave.
91- with cython.critical_section(self , self .addresses):
89+ # We need a critical section here so that addresses and size get
90+ # updated atomically. If we were to acquire a critical section on self,
91+ # mutating the dictionary would try to acquire a critical section on
92+ # the dictionary and therefore release the critical section on self.
93+ # Acquiring a critical section on self.addresses works because that's
94+ # the only C API that gets called inside the block and acquiring the
95+ # critical section on the top-most held lock does not release it.
96+ with cython.critical_section(self .addresses):
9297 self .addresses[< size_t> p] = number * elem_size
9398 self .size += number * elem_size
9499 return p
@@ -108,18 +113,18 @@ cdef class Pool:
108113 if new_ptr == NULL :
109114 raise MemoryError (" Error reallocating to %d bytes" % new_size)
110115
111- # We need a critical section on both self and self.addresses here.
112- # See comment in alloc for rationale .
113- with cython.critical_section(self , self .addresses):
116+ # See comment in alloc on why we're acquiring a critical section on
117+ # self.addresses instead of self .
118+ with cython.critical_section(self .addresses):
114119 try :
115120 old_size = self .addresses.pop(< size_t> p)
116121 except KeyError :
117122 self .pyfree.free(new_ptr)
118123 raise ValueError (" Pointer %d not found in Pool %s " % (< size_t> p, self .addresses))
119124
120125 if old_size >= new_size:
121- self .pyfree.free(new_ptr)
122126 self .addresses[< size_t> p] = old_size
127+ self .pyfree.free(new_ptr)
123128 raise ValueError (" Realloc requires new_size > previous size" )
124129
125130 memcpy(new_ptr, p, old_size)
@@ -138,13 +143,10 @@ cdef class Pool:
138143
139144 If p is not in Pool.addresses, a KeyError is raised.
140145 """
141- # We need a critical section on both self and self.addresses here.
142- # See comment in alloc for rationale.
143- with cython.critical_section(self , self .addresses):
144- # The cast to size_t below is needed so that Cython
145- # does not call into the C API to do the subtraction,
146- # which could potentially release the critical section.
147- self .size -= < size_t> self .addresses.pop(< size_t> p)
146+ # See comment in alloc on why we're acquiring a critical section on
147+ # self.addresses instead of self.
148+ with cython.critical_section(self .addresses):
149+ self .size -= self .addresses.pop(< size_t> p)
148150 self .pyfree.free(p)
149151
150152 def own_pyref (self , object py_ref ):
0 commit comments