Skip to content

Commit 5dd78bc

Browse files
Merge pull request #39 from mjp41/cown-arguments-to-threads
Phase 3: Check thread arguments
2 parents 68a9b70 + c9ef79d commit 5dd78bc

4 files changed

Lines changed: 85 additions & 0 deletions

File tree

Include/regions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ PyAPI_FUNC(int) _Py_IsLocal(PyObject *op);
1515
PyAPI_FUNC(int) _Py_IsCown(PyObject *op);
1616
#define Py_IsCown(op) _Py_IsCown(_PyObject_CAST(op))
1717

18+
int Py_is_invariant_enabled(void);
19+
1820
#ifdef __cplusplus
1921
}
2022
#endif

Lib/test/test_using.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,25 @@ def _():
166166
result = c.get().value.value
167167
if result != 200:
168168
self.fail()
169+
170+
def test_thread_creation(self):
171+
from using import PyronaThread as T
172+
173+
class Mutable: pass
174+
self.assertRaises(RuntimeError, lambda x: T(target=print, args=(Mutable(),)), None)
175+
self.assertRaises(RuntimeError, lambda x: T(target=print, kwargs={'a' : Mutable()}), None)
176+
self.assertRaises(RuntimeError, lambda x: T(target=print, args=(Mutable(),), kwargs={'a' : Mutable()}), None)
177+
self.assertRaises(RuntimeError, lambda x: T(target=print, args=(Mutable(), 42)), None)
178+
self.assertRaises(RuntimeError, lambda x: T(target=print, args=(Mutable(), Cown())), None)
179+
self.assertRaises(RuntimeError, lambda x: T(target=print, args=(Mutable(), Region())), None)
180+
181+
T(target=print, kwargs={'imm' : 42, 'cown' : Cown(), 'region' : Region()})
182+
T(target=print, kwargs={'a': 42})
183+
T(target=print, kwargs={'a': Cown()})
184+
T(target=print, kwargs={'a': Region()})
185+
186+
T(target=print, args=(42, Cown(), Region()))
187+
T(target=print, args=(42,))
188+
T(target=print, args=(Cown(),))
189+
T(target=print, args=(Region(),))
190+
self.assertTrue(True) # To make sure we got here correctly

Lib/using.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,58 @@ def decorator(func):
4545
with CS(cowns, *args):
4646
return func()
4747
return decorator
48+
49+
# TODO: this creates a normal Python thread and ensures that all its
50+
# arguments are moved to the new thread. Eventually we should revisit
51+
# this behaviour as we go multiple interpreters / multicore.
52+
# TODO: require RC to be one less when move is upstreamed
53+
def PyronaThread(group=None, target=None, name=None,
54+
args=(), kwargs=None, *, daemon=None):
55+
# Only check when a program uses pyrona
56+
from sys import getrefcount as rc
57+
from threading import Thread
58+
# TODO: improve this check for final version of phase 3
59+
# - Revisit the rc checks
60+
# - Consider throwing a different kind of error (e.g. RegionError)
61+
# - Improve error messages
62+
def ok_share(o):
63+
if isimmutable(o):
64+
return True
65+
if isinstance(o, Cown):
66+
return True
67+
return False
68+
def ok_move(o):
69+
if isinstance(o, Region):
70+
if rc(o) != 5:
71+
# rc = 4 because:
72+
# 1. ref to o in rc
73+
# 2. ref to o on this frame (ok_move)
74+
# 3. ref to o on the calling frame (check)
75+
# 4. ref to o from iteration over kwargs dictionary or args tuple/list
76+
# 5. ref to o from kwargs dictionary or args tuple/list
77+
raise RuntimeError("Region passed to thread was not moved into thread")
78+
if o.is_open():
79+
raise RuntimeError("Region passed to thread was open")
80+
return True
81+
return False
82+
83+
def check(a, args):
84+
# rc(args) == 4 because we need to know that the args list is moved into the thread too
85+
# rc = 4 because:
86+
# 1. ref to args in rc
87+
# 2. ref to args on this frame
88+
# 3. ref to args on the calling framedef check(a, args):
89+
# 4. ref from frame calling PyronaThread -- FIXME: not valid; revisit after #45
90+
if not (ok_share(a) or (ok_move(a) and rc(args) == 4)):
91+
raise RuntimeError("Thread was passed an object which was neither immutable, a cown, or a unique region")
92+
93+
if kwargs is None:
94+
for a in args:
95+
check(a, args)
96+
return Thread(group, target, name, args, daemon)
97+
else:
98+
for k in kwargs:
99+
# Important to get matching RCs in both paths
100+
v = kwargs[k]
101+
check(v, kwargs)
102+
return Thread(group, target, name, kwargs, daemon)

Objects/regions.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ static void _PyErr_Region(PyObject *src, PyObject *tgt, const char *msg);
5050
* Global status for performing the region check.
5151
*/
5252
bool invariant_do_region_check = false;
53+
/**
54+
* TODO: revisit the definition of this builting function
55+
*/
56+
int Py_is_invariant_enabled(void) {
57+
return invariant_do_region_check;
58+
}
5359

5460
// The src object for an edge that invalidated the invariant.
5561
PyObject* invariant_error_src = Py_None;

0 commit comments

Comments
 (0)