Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phase 3: Check thread arguments #39

Merged
merged 13 commits into from
Feb 2, 2025
2 changes: 2 additions & 0 deletions Include/regions.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ PyAPI_FUNC(int) _Py_IsLocal(PyObject *op);
PyAPI_FUNC(int) _Py_IsCown(PyObject *op);
#define Py_IsCown(op) _Py_IsCown(_PyObject_CAST(op))

int Py_is_invariant_enabled(void);

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_using.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,15 @@ def _():
result = c.get().value.value
if result != 200:
self.fail()

def test_thread_creation(self):
from using import PyronaThread as T

class Mutable: pass
self.assertRaises(RuntimeError, T, kwargs = { 'target' : print, 'args' : (Mutable(),) })
self.assertRaises(RuntimeError, T, kwargs = { 'target' : print, 'kwargs' : {'a' : Mutable()} })
self.assertRaises(RuntimeError, T, kwargs = { 'target' : print, 'args' : (Mutable(),), 'kwargs' : {'a' : Mutable()} })

T(target=print, args=(42, Cown(), Region()))
T(target=print, kwargs={'imm' : 42, 'cown' : Cown(), 'region' : Region()})
self.assertTrue(True) # To make sure we got here correctly
54 changes: 54 additions & 0 deletions Lib/using.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,57 @@ def decorator(func):
with CS(cowns, *args):
return func()
return decorator

# TODO: this creates a normal Python thread and ensures that all its
# arguments are moved to the new thread. Eventually we should revisit
# this behaviour as we go multiple interpreters / multicore.
def PyronaThread(group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
# Only check when a program uses pyrona
from sys import getrefcount as rc
from threading import Thread
# TODO: improve this check for final version of phase 3
# - Revisit the rc checks
# - Consider throwing a different kind of error (e.g. RegionError)
# - Improve error messages
def ok_share(o):
if isimmutable(o):
return True
if isinstance(o, Cown):
return True
return False
def ok_move(o):
if isinstance(o, Region):
if rc(o) != 4:
# rc = 4 because:
# 1. ref to o in rc
# 2. ref to o on this frame
# 3. ref to o on the calling frame
# 4. ref to o from kwargs dictionary or args tuple/list
raise RuntimeError("Region passed to thread was not moved into thread")
if o.is_open():
raise RuntimeError("Region passed to thread was open")
return True
return False

if kwargs is None:
for a in args:
# rc(args) == 3 because we need to know that the args list is moved into the thread too
# rc = 3 because:
# 1. ref to args in rc
# 2. ref to args on this frame
# 3. ref to args on the calling frame
if not (ok_share(a) or (ok_move(a) and rc(args) == 3)):
raise RuntimeError("Thread was passed an object which was neither immutable, a cown, or a unique region")
return Thread(group, target, name, args, daemon)
else:
for k in kwargs:
# rc(args) == 3 because we need to know that keyword dict is moved into the thread too
# rc = 3 because:
# 1. ref to kwargs in rc
# 2. ref to kwargs on this frame
# 3. ref to kwargs on the calling frame
v = kwargs[k]
if not (ok_share(v) or (ok_move(v) and rc(kwargs) == 3)):
raise RuntimeError("Thread was passed an object which was neither immutable, a cown, or a unique region")
return Thread(group, target, name, kwargs, daemon)
6 changes: 6 additions & 0 deletions Objects/regions.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ static void _PyErr_Region(PyObject *src, PyObject *tgt, const char *msg);
* Global status for performing the region check.
*/
bool invariant_do_region_check = false;
/**
* TODO: revisit the definition of this builting function
*/
int Py_is_invariant_enabled(void) {
return invariant_do_region_check;
}

// The src object for an edge that invalidated the invariant.
PyObject* invariant_error_src = Py_None;
Expand Down
20 changes: 20 additions & 0 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2815,6 +2815,25 @@ builtin_enableinvariant_impl(PyObject *module)
return Py_EnableInvariant();
}

/*[clinic input]
is_pyrona_program as builtin_is_pyrona_program
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I this used anymore? Should it still be part of this PR? I think now there is a separate API, we don't need to expose this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I will remove it!


Returns True if the program has used regions or cowns.
[clinic start generated code]*/

static PyObject *
builtin_is_pyrona_program_impl(PyObject *module)
/*[clinic end generated code: output=2b6729469da00221 input=d36655a3dc34a881]*/
{
// TODO: This is an abuse of notions. We need to revisit
// the definition of when a program is a Pyrona program
// at some later point. The reason for having the definition
// conflated with the invariant being enabled is to only
// perform Pyrona checks (see threading.py) when a program
// must adhere to these checks for correctness.
return Py_is_invariant_enabled() ? Py_True : Py_False;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does feel like a bit of abuse of notions. I think putting a TODO here, se that we don't merge phase 3 without revisiting. Really I think we want a has used Pyrona, and that is the name of the flag that is used to determine if we should do the region check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree. Will add the TODO as you suggest.

}

typedef struct {
PyObject_HEAD
Py_ssize_t tuplesize;
Expand Down Expand Up @@ -3116,6 +3135,7 @@ static PyMethodDef builtin_methods[] = {
BUILTIN_ISSUBCLASS_METHODDEF
BUILTIN_ISIMMUTABLE_METHODDEF
BUILTIN_MAKEIMMUTABLE_METHODDEF
BUILTIN_IS_PYRONA_PROGRAM_METHODDEF
BUILTIN_INVARIANTSRCFAILURE_METHODDEF
BUILTIN_INVARIANTTGTFAILURE_METHODDEF
BUILTIN_ITER_METHODDEF
Expand Down
20 changes: 19 additions & 1 deletion Python/clinic/bltinmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading