Skip to content

Commit da00da6

Browse files
Cown release (#44)
* Cown release A cown will now be released: - When it is in pending-release state and its containing region closes - When a closed region, other cown or immutable object it stored in it - At the end of a @using if the cown's contained region can be closed * TODO => TODO Pyrona: Co-authored-by: Fridtjof Stoldt <xFrednet@gmail.com> * Remove GIL operations in release (since this operation cannot be contended) --------- Co-authored-by: Fridtjof Stoldt <xFrednet@gmail.com>
1 parent 47f2fd5 commit da00da6

4 files changed

Lines changed: 53 additions & 18 deletions

File tree

Include/internal/pycore_regions.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,13 @@ int _Py_CheckRegionInvariant(PyThreadState *tstate);
8585
// Set a cown as parent of a region
8686
void _PyRegion_set_cown_parent(PyObject* region, PyObject* cown);
8787
// Check whether a region is closed
88-
int _PyRegion_is_closed(PyObject* region);
8988
int _PyCown_release(PyObject *self);
9089
int _PyCown_is_released(PyObject *self);
90+
int _PyCown_is_pending_release(PyObject *self);
91+
PyObject *_PyCown_close_region(PyObject* ob);
92+
#define PyCown_close_region(op) _PyCown_close_region(_PyObject_CAST(op))
93+
int _PyRegion_is_closed(PyObject* op);
94+
#define PyRegion_is_closed(op) _PyRegion_is_closed(_PyObject_CAST(op))
9195

9296

9397
#ifdef _Py_TYPEOF

Lib/test/test_using.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def _():
9696
self.assertTrue(self.hacky_state_check(c, "acquired"))
9797
r = None
9898
c.get().close()
99-
self.assertTrue(self.hacky_state_check(c, "released"))
99+
self.assertTrue(self.hacky_state_check(c, "acquired"))
100100
self.assertTrue(self.hacky_state_check(c, "released"))
101101

102102
def test_region_cown_ptr(self):

Objects/cown.c

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include "pyerrors.h"
2020
#include "pystate.h"
2121

22+
// Needed to test for region object
23+
extern PyTypeObject PyRegion_Type;
24+
extern PyTypeObject PyCown_Type;
25+
2226
typedef enum {
2327
Cown_RELEASED = 0,
2428
Cown_ACQUIRED = 1,
@@ -57,7 +61,7 @@ static void PyCown_dealloc(PyCownObject *self) {
5761
}
5862

5963
static int PyCown_init(PyCownObject *self, PyObject *args, PyObject *kwds) {
60-
// TODO: should not be needed in the future
64+
// TODO: Pyrona: should not be needed in the future
6165
_Py_MakeImmutable(_PyObject_CAST(Py_TYPE(self)));
6266
_Py_notify_regions_in_use();
6367

@@ -128,10 +132,12 @@ static int PyCown_traverse(PyCownObject *self, visitproc visit, void *arg) {
128132
// compatible with PyCFunction
129133
static PyObject *PyCown_acquire(PyCownObject *self, PyObject *ignored) {
130134
PyThreadState *tstate = PyThreadState_Get();
135+
136+
// TODO: Pyrona: releasing the GIL will eventually not be necessary here
131137
Py_BEGIN_ALLOW_THREADS
132138
int expected = Cown_RELEASED;
133139

134-
// TODO: eventually replace this with something from pycore_atomic (nothing there now)
140+
// TODO: Pyrona: eventually replace this with something from pycore_atomic (nothing there now)
135141
while (!atomic_compare_exchange_strong(&self->state._value, &expected, Cown_ACQUIRED)) {
136142
expected = Cown_RELEASED;
137143
sem_wait(&self->semaphore);
@@ -155,11 +161,16 @@ static PyObject *PyCown_release(PyCownObject *self, PyObject *ignored) {
155161

156162
BAIL_UNLESS_OWNED(self, "Thread attempted to release a cown it did not own");
157163

158-
Py_BEGIN_ALLOW_THREADS
164+
if (self->value && Py_TYPE(self->value) == &PyRegion_Type) {
165+
if (PyCown_close_region(self->value) == NULL) {
166+
// Close region failed -- propagate its error
167+
return NULL;
168+
}
169+
}
170+
159171
self->owning_thread = 0;
160172
_Py_atomic_store(&self->state, Cown_RELEASED);
161173
sem_post(&self->semaphore);
162-
Py_END_ALLOW_THREADS
163174

164175
Py_RETURN_NONE;
165176
}
@@ -174,6 +185,13 @@ int _PyCown_is_released(PyObject *self) {
174185
return STATE(cown) == Cown_RELEASED;
175186
}
176187

188+
int _PyCown_is_pending_release(PyObject *self) {
189+
assert(Py_TYPE(self) == &PyCown_Type && "Is pending release called on non-cown!");
190+
191+
PyCownObject *cown = _Py_CAST(PyCownObject *, self);
192+
return STATE(cown) == Cown_PENDING_RELEASE;
193+
}
194+
177195
// The ignored argument is required for this function's type to be
178196
// compatible with PyCFunction
179197
static PyObject *PyCown_get(PyCownObject *self, PyObject *ignored) {
@@ -186,16 +204,12 @@ static PyObject *PyCown_get(PyCownObject *self, PyObject *ignored) {
186204
}
187205
}
188206

189-
// Needed to test for region object
190-
extern PyTypeObject PyRegion_Type;
191-
extern PyTypeObject PyCown_Type;
192-
193207
static PyObject *PyCown_set_unchecked(PyCownObject *self, PyObject *arg) {
194208
// Cowns are cells that hold a reference to a bridge object,
195209
// (or another cown or immutable object)
196-
const bool is_region_object =
210+
const bool arg_is_region_object =
197211
Py_IS_TYPE(arg, &PyRegion_Type) && _Py_is_bridge_object(arg);
198-
if (is_region_object ||
212+
if (arg_is_region_object ||
199213
arg->ob_type == &PyCown_Type ||
200214
_Py_IsImmutable(arg)) {
201215

@@ -205,10 +219,14 @@ static PyObject *PyCown_set_unchecked(PyCownObject *self, PyObject *arg) {
205219

206220
// Tell the region that it is owned by a cown,
207221
// to enable it to release the cown on close
208-
if (is_region_object) {
222+
if (arg_is_region_object) {
209223
_PyRegion_set_cown_parent(arg, _PyObject_CAST(self));
224+
// TODO: Pyrona: should not run try close here unless dirty at the end of phase 3
225+
// if (_PyCown_close_region(arg) == Py_None) {
210226
if (_PyRegion_is_closed(arg)) {
211-
PyCown_release(self, NULL);
227+
if (PyCown_release(self, NULL) == NULL) {
228+
PyErr_Clear();
229+
}
212230
} else {
213231
_Py_atomic_store(&self->state, Cown_PENDING_RELEASE);
214232
PyThreadState *tstate = PyThreadState_Get();

Objects/regions.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ static int regionmetadata_close(regionmetadata* self) {
219219
return regionmetadata_dec_osc(parent);
220220
}
221221

222-
// Check if in a cown -- if so, release cown
223-
if (self->cown) {
222+
// Check if in a cown which is waiting for the region to close -- if so, release cown
223+
if (self->cown && _PyCown_is_pending_release(self->cown)) {
224224
// Propagate error from release
225225
return _PyCown_release(self->cown);
226226
}
@@ -1749,13 +1749,16 @@ int _PyRegion_is_closed(PyObject* self) {
17491749
// The ignored argument is required for this function's type to be
17501750
// compatible with PyCFunction
17511751
static PyObject *PyRegion_close(PyRegionObject *self, PyObject *ignored) {
1752-
regionmetadata* const md = REGION_DATA_CAST(Py_REGION(self));
1753-
if (!regionmetadata_is_open(md)) {
1752+
if (PyRegion_is_closed(self)) {
17541753
Py_RETURN_NONE; // Double close is OK
17551754
}
17561755

17571756
// Attempt to close the region
17581757
if (try_close(self) != 0) {
1758+
if (!PyErr_Occurred()) {
1759+
// try_close did not run out of memory but failed to close the region
1760+
PyErr_Format(PyExc_RegionError, "Attempting to close the region failed");
1761+
}
17591762
return NULL;
17601763
}
17611764

@@ -2009,3 +2012,13 @@ void _Py_RegionRemoveReference(PyObject *src, PyObject *tgt) {
20092012
// Unparent the region.
20102013
regionmetadata_set_parent(tgt_md, NULL);
20112014
}
2015+
2016+
PyObject *_PyCown_close_region(PyObject* ob) {
2017+
if (Py_TYPE(ob) == &PyRegion_Type) {
2018+
// Attempt to close the region
2019+
return PyRegion_close(_Py_CAST(PyRegionObject*, ob), NULL);
2020+
} else {
2021+
PyErr_SetString(PyExc_RegionError, "Attempted to close a region through a non-bridge object");
2022+
return NULL;
2023+
}
2024+
}

0 commit comments

Comments
 (0)