Skip to content

Commit c34f476

Browse files
committed
Acquire critical section only on self.addresses
1 parent 53bb400 commit c34f476

File tree

1 file changed

+19
-17
lines changed

1 file changed

+19
-17
lines changed

cymem/cymem.pyx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)