Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
nexedi
cython
Commits
a80ce036
Commit
a80ce036
authored
Dec 10, 2020
by
Xavier Thompson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduce 'locked' qualifier and refactor locking
parent
a66ffc46
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
79 additions
and
561 deletions
+79
-561
Cython/Compiler/Builtin.py
Cython/Compiler/Builtin.py
+5
-5
Cython/Compiler/CypclassTransforms.py
Cython/Compiler/CypclassTransforms.py
+26
-397
Cython/Compiler/ExprNodes.py
Cython/Compiler/ExprNodes.py
+8
-7
Cython/Compiler/ModuleNode.py
Cython/Compiler/ModuleNode.py
+0
-7
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+6
-4
Cython/Compiler/Parsing.py
Cython/Compiler/Parsing.py
+2
-12
Cython/Compiler/PyrexTypes.py
Cython/Compiler/PyrexTypes.py
+3
-4
Cython/Compiler/Symtab.py
Cython/Compiler/Symtab.py
+4
-2
tests/errors/cypclass_lock_error.pyx
tests/errors/cypclass_lock_error.pyx
+0
-43
tests/run/cypclass_acthon.pyx
tests/run/cypclass_acthon.pyx
+16
-18
tests/run/cypclass_exception_refcount.pyx
tests/run/cypclass_exception_refcount.pyx
+1
-1
tests/run/cypclass_inplace_operator_inference.pyx
tests/run/cypclass_inplace_operator_inference.pyx
+5
-5
tests/run/cypclass_lock.pyx
tests/run/cypclass_lock.pyx
+0
-53
tests/run/cypclass_nested_classes.pyx
tests/run/cypclass_nested_classes.pyx
+3
-3
No files found.
Cython/Compiler/Builtin.py
View file @
a80ce036
...
...
@@ -415,7 +415,7 @@ def inject_acthon_interfaces(self):
init_scope
(
result_scope
)
acthon_result_type
=
result_type
=
PyrexTypes
.
CypClassType
(
"ActhonResultInterface"
,
result_scope
,
"ActhonResultInterface"
,
(
PyrexTypes
.
cy_object_type
,),
lock_mode
=
"autolock"
,
activable
=
False
)
activable
=
False
)
result_scope
.
type
=
result_type
#result_type.set_scope is required because parent_type is used when doing scope inheritance
result_type
.
set_scope
(
result_scope
)
...
...
@@ -474,7 +474,7 @@ def inject_acthon_interfaces(self):
init_scope
(
message_scope
)
acthon_message_type
=
message_type
=
PyrexTypes
.
CypClassType
(
"ActhonMessageInterface"
,
message_scope
,
"ActhonMessageInterface"
,
(
PyrexTypes
.
cy_object_type
,),
lock_mode
=
"autolock"
,
activable
=
False
)
activable
=
False
)
message_type
.
set_scope
(
message_scope
)
message_scope
.
type
=
message_type
...
...
@@ -488,7 +488,7 @@ def inject_acthon_interfaces(self):
init_scope
(
sync_scope
)
acthon_sync_type
=
sync_type
=
PyrexTypes
.
CypClassType
(
"ActhonSyncInterface"
,
sync_scope
,
"ActhonSyncInterface"
,
(
PyrexTypes
.
cy_object_type
,),
lock_mode
=
"autolock"
,
activable
=
False
)
activable
=
False
)
sync_type
.
set_scope
(
sync_scope
)
sync_scope
.
type
=
sync_type
sync_entry
=
self
.
declare
(
"ActhonSyncInterface"
,
"ActhonSyncInterface"
,
sync_type
,
None
,
"extern"
)
...
...
@@ -557,7 +557,7 @@ def inject_acthon_interfaces(self):
init_scope
(
queue_scope
)
acthon_queue_type
=
queue_type
=
PyrexTypes
.
CypClassType
(
"ActhonQueueInterface"
,
queue_scope
,
"ActhonQueueInterface"
,
(
PyrexTypes
.
cy_object_type
,),
lock_mode
=
"autolock"
,
activable
=
False
)
activable
=
False
)
queue_type
.
set_scope
(
queue_scope
)
queue_scope
.
type
=
queue_type
queue_entry
=
self
.
declare
(
"ActhonQueueInterface"
,
"ActhonQueueInterface"
,
queue_type
,
self
,
"extern"
)
...
...
@@ -594,7 +594,7 @@ def inject_acthon_interfaces(self):
init_scope
(
activable_scope
)
acthon_activable_type
=
activable_type
=
PyrexTypes
.
CypClassType
(
"ActhonActivableClass"
,
activable_scope
,
"ActhonActivableClass"
,
(
PyrexTypes
.
cy_object_type
,),
lock_mode
=
"autolock"
,
activable
=
False
)
activable
=
False
)
activable_type
.
set_scope
(
activable_scope
)
activable_entry
=
self
.
declare
(
"ActhonActivableClass"
,
None
,
activable_type
,
"ActhonActivableClass"
,
"extern"
)
activable_entry
.
is_type
=
1
...
...
Cython/Compiler/CypclassTransforms.py
View file @
a80ce036
...
...
@@ -36,7 +36,7 @@ class CypclassWrapperInjection(Visitor.CythonTransform):
"""
# property templates
unlocked_property
=
TreeFragment
.
TreeFragment
(
u"""
property_template
=
TreeFragment
.
TreeFragment
(
u"""
property NAME:
def __get__(self):
OBJ = <TYPE> self
...
...
@@ -46,19 +46,6 @@ property NAME:
OBJ.ATTR = value
"""
,
level
=
'c_class'
,
pipeline
=
[
NormalizeTree
(
None
)])
locked_property
=
TreeFragment
.
TreeFragment
(
u"""
property NAME:
def __get__(self):
OBJ = <TYPE> self
with rlocked OBJ:
value = OBJ.ATTR
return value
def __set__(self, value):
OBJ = <TYPE> self
with wlocked OBJ:
OBJ.ATTR = value
"""
,
level
=
'c_class'
,
pipeline
=
[
NormalizeTree
(
None
)])
# method wrapper templates
method_template
=
TreeFragment
.
TreeFragment
(
u"""
def NAME(self, ARGDECLS):
...
...
@@ -73,13 +60,13 @@ def NAME(self, ARGDECLS):
"""
,
level
=
'c_class'
,
pipeline
=
[
NormalizeTree
(
None
)])
# static method wrapper templates
static_
method
=
TreeFragment
.
TreeFragment
(
u"""
static_
template
=
TreeFragment
.
TreeFragment
(
u"""
@staticmethod
def NAME(ARGDECLS):
return TYPE_NAME.NAME(ARGS)
"""
,
level
=
'c_class'
,
pipeline
=
[
NormalizeTree
(
None
)])
static_
method_no_return
=
TreeFragment
.
TreeFragment
(
u"""
static_
no_return_template
=
TreeFragment
.
TreeFragment
(
u"""
@staticmethod
def NAME(ARGDECLS):
TYPE_NAME.NAME(ARGS)
...
...
@@ -293,10 +280,7 @@ def NAME(ARGDECLS):
return
None
if
not
attr_entry
.
type
.
can_coerce_from_pyobject
(
self
.
module_scope
):
return
None
if
node_entry
.
type
.
lock_mode
==
'checklock'
:
template
=
self
.
locked_property
else
:
template
=
self
.
unlocked_property
template
=
self
.
property_template
underlying_name
=
EncodedString
(
"o"
)
property
=
template
.
substitute
({
"ATTR"
:
attr_entry
.
name
,
...
...
@@ -318,15 +302,6 @@ def NAME(ARGDECLS):
method_type
=
method_entry
.
type
if
not
method_type
.
is_static_method
and
node
.
entry
.
type
.
lock_mode
==
"checklock"
:
# skip non-static methods with "checklock" self
return
for
argtype
in
method_type
.
args
:
if
argtype
.
type
.
is_cyp_class
and
argtype
.
type
.
lock_mode
==
"checklock"
:
# skip methods with "checklock" cypclass arguments
return
if
method_type
.
optional_arg_count
:
# for now skip methods with optional arguments
return
...
...
@@ -395,7 +370,7 @@ def NAME(ARGDECLS):
need_return
=
not
return_type
.
is_void
if
method_entry
.
type
.
is_static_method
:
template
=
self
.
static_
method
if
need_return
else
self
.
static_method_no_return
template
=
self
.
static_
template
if
need_return
else
self
.
static_no_return_template
method_wrapper
=
template
.
substitute
({
"NAME"
:
method_name
,
...
...
@@ -426,382 +401,36 @@ def NAME(ARGDECLS):
class
CypclassLockTransform
(
Visitor
.
EnvTransform
):
"""
Check that cypclass objects are properly locked and insert locks if required.
"""
class
StackLock
:
"""
Context manager for tracking nested locks.
Acquire cypclass locks where required.
"""
def
__init__
(
self
,
transform
,
obj_entry
,
state
):
self
.
transform
=
transform
self
.
state
=
state
self
.
entry
=
obj_entry
def
__enter__
(
self
):
state
=
self
.
state
entry
=
self
.
entry
self
.
old_rlocked
=
self
.
transform
.
rlocked
[
entry
]
self
.
old_wlocked
=
self
.
transform
.
wlocked
[
entry
]
if
state
==
'rlocked'
:
self
.
transform
.
rlocked
[
entry
]
+=
1
elif
state
==
'wlocked'
:
self
.
transform
.
wlocked
[
entry
]
+=
1
def
__exit__
(
self
,
*
args
):
entry
=
self
.
entry
self
.
transform
.
rlocked
[
entry
]
=
self
.
old_rlocked
self
.
transform
.
wlocked
[
entry
]
=
self
.
old_wlocked
def
stacklock
(
self
,
obj_entry
,
state
):
return
self
.
StackLock
(
self
,
obj_entry
,
state
)
def
with_nested_stacklocks
(
self
,
stacklocks_iterator
,
body_callback
):
# Poor mans's nested context managers
try
:
stacklock
=
next
(
stacklocks_iterator
)
except
StopIteration
:
return
body_callback
()
with
stacklock
:
return
self
.
with_nested_stacklocks
(
stacklocks_iterator
,
body_callback
)
class
AccessContext
:
"""
Context manager to track the kind of access (reading, writing ...).
"""
def
__init__
(
self
,
collector
,
reading
=
False
,
writing
=
False
,
deleting
=
False
):
self
.
collector
=
collector
self
.
reading
=
reading
self
.
writing
=
writing
self
.
deleting
=
deleting
def
__enter__
(
self
):
self
.
reading
,
self
.
collector
.
reading
=
self
.
collector
.
reading
,
self
.
reading
self
.
writing
,
self
.
collector
.
writing
=
self
.
collector
.
writing
,
self
.
writing
self
.
deleting
,
self
.
collector
.
deleting
=
self
.
collector
.
deleting
,
self
.
deleting
def
__exit__
(
self
,
*
args
):
self
.
collector
.
reading
=
self
.
reading
self
.
collector
.
writing
=
self
.
writing
self
.
collector
.
deleting
=
self
.
deleting
def
accesscontext
(
self
,
reading
=
False
,
writing
=
False
,
deleting
=
False
):
return
self
.
AccessContext
(
self
,
reading
=
reading
,
writing
=
writing
,
deleting
=
deleting
)
def
__call__
(
self
,
root
):
self
.
rlocked
=
defaultdict
(
int
)
self
.
wlocked
=
defaultdict
(
int
)
self
.
reading
=
False
self
.
writing
=
False
self
.
deleting
=
False
return
super
(
CypclassLockTransform
,
self
).
__call__
(
root
)
def
reference_identifier
(
self
,
node
):
while
isinstance
(
node
,
ExprNodes
.
CoerceToTempNode
):
node
=
node
.
arg
if
node
.
is_name
:
return
node
.
entry
return
None
def
id_to_name
(
self
,
id
):
return
id
.
name
def
lockcheck_on_context
(
self
,
node
):
if
self
.
writing
or
self
.
deleting
:
return
self
.
lockcheck_written
(
node
)
elif
self
.
reading
:
return
self
.
lockcheck_read
(
node
)
return
node
def
lockcheck_read
(
self
,
read_node
):
lock_mode
=
read_node
.
type
.
lock_mode
if
lock_mode
==
"nolock"
:
return
read_node
ref_id
=
self
.
reference_identifier
(
read_node
)
if
ref_id
:
if
not
(
self
.
rlocked
[
ref_id
]
>
0
or
self
.
wlocked
[
ref_id
]
>
0
):
if
lock_mode
==
"checklock"
:
error
(
read_node
.
pos
,
(
"Reference '%s' is not correctly locked in this expression (read lock required)"
)
%
self
.
id_to_name
(
ref_id
)
)
elif
lock_mode
==
"autolock"
:
# for now, lock a temporary for each expression
return
ExprNodes
.
CoerceToLockedNode
(
read_node
,
self
.
current_env
(),
rlock_only
=
True
)
else
:
if
lock_mode
==
"checklock"
:
error
(
read_node
.
pos
,
"This expression is not correctly locked (read lock required)"
)
elif
lock_mode
==
"autolock"
:
if
not
isinstance
(
read_node
,
ExprNodes
.
CoerceToLockedNode
):
return
ExprNodes
.
CoerceToLockedNode
(
read_node
,
self
.
current_env
(),
rlock_only
=
True
)
return
read_node
def
lockcheck_written
(
self
,
written_node
):
lock_mode
=
written_node
.
type
.
lock_mode
if
lock_mode
==
"nolock"
:
return
written_node
ref_id
=
self
.
reference_identifier
(
written_node
)
if
ref_id
:
if
not
self
.
wlocked
[
ref_id
]
>
0
:
if
lock_mode
==
"checklock"
:
error
(
written_node
.
pos
,
(
"Reference '%s' is not correctly locked in this expression (write lock required)"
)
%
self
.
id_to_name
(
ref_id
)
)
elif
lock_mode
==
"autolock"
:
# for now, lock a temporary for each expression
return
ExprNodes
.
CoerceToLockedNode
(
written_node
,
self
.
current_env
(),
rlock_only
=
False
)
else
:
if
lock_mode
==
"checklock"
:
error
(
written_node
.
pos
,
"This expression is not correctly locked (write lock required)"
)
elif
lock_mode
==
"autolock"
:
if
isinstance
(
written_node
,
ExprNodes
.
CoerceToLockedNode
):
written_node
.
rlock_only
=
False
else
:
return
ExprNodes
.
CoerceToLockedNode
(
written_node
,
self
.
current_env
())
return
written_node
def
lockcheck_written_or_read
(
self
,
node
,
reading
=
False
):
if
reading
:
return
self
.
lockcheck_read
(
node
)
else
:
return
self
.
lockcheck_written
(
node
)
return
node
def
lockcheck_if_subscript_rhs
(
self
,
lhs
,
rhs
):
if
lhs
.
is_subscript
and
lhs
.
base
.
type
.
is_cyp_class
:
setitem
=
lhs
.
base
.
type
.
scope
.
lookup
(
"__setitem__"
)
if
setitem
and
len
(
setitem
.
type
.
args
)
==
2
:
arg_type
=
setitem
.
type
.
args
[
1
].
type
if
arg_type
.
is_cyp_class
:
return
self
.
lockcheck_written_or_read
(
rhs
,
reading
=
arg_type
.
is_const_cyp_class
)
# else: should have caused a previous error
return
rhs
def
visit_CFuncDefNode
(
self
,
node
):
cyp_class_args
=
(
e
for
e
in
node
.
local_scope
.
arg_entries
if
e
.
type
.
is_cyp_class
)
arg_locks
=
[]
for
arg
in
cyp_class_args
:
# Mark each cypclass arguments as locked within the function body
arg_locks
.
append
(
self
.
stacklock
(
arg
,
"rlocked"
if
arg
.
type
.
is_const_cyp_class
else
"wlocked"
))
with_body
=
lambda
:
self
.
visit
(
node
.
body
)
self
.
with_nested_stacklocks
(
iter
(
arg_locks
),
with_body
)
return
node
def
visit_LockCypclassNode
(
self
,
node
):
obj_ref_id
=
self
.
reference_identifier
(
node
.
obj
)
if
not
obj_ref_id
:
error
(
node
.
obj
.
pos
,
"Locking an unnamed reference"
)
return
node
if
not
node
.
obj
.
type
.
is_cyp_class
:
error
(
node
.
obj
.
pos
,
"Locking non-cypclass reference"
)
return
node
if
not
(
obj_ref_id
.
is_local
or
obj_ref_id
.
is_arg
or
obj_ref_id
.
is_self_arg
):
error
(
node
.
obj
.
pos
,
"Can only lock local variables or arguments"
)
return
node
with
self
.
stacklock
(
obj_ref_id
,
node
.
state
):
self
.
visit
(
node
.
body
)
return
node
def
visit_Node
(
self
,
node
):
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_DelStatNode
(
self
,
node
):
for
arg
in
node
.
args
:
arg_ref_id
=
self
.
reference_identifier
(
arg
)
if
self
.
rlocked
[
arg_ref_id
]
>
0
or
self
.
wlocked
[
arg_ref_id
]
>
0
:
# Disallow unbinding a locked name
error
(
arg
.
pos
,
"Deleting a locked cypclass reference"
)
return
node
with
self
.
accesscontext
(
deleting
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_SingleAssignmentNode
(
self
,
node
):
lhs_ref_id
=
self
.
reference_identifier
(
node
.
lhs
)
if
self
.
rlocked
[
lhs_ref_id
]
>
0
or
self
.
wlocked
[
lhs_ref_id
]
>
0
:
# Disallow re-binding a locked name
error
(
node
.
lhs
.
pos
,
"Assigning to a locked cypclass reference"
)
return
node
node
.
rhs
=
self
.
lockcheck_if_subscript_rhs
(
node
.
lhs
,
node
.
rhs
)
with
self
.
accesscontext
(
writing
=
True
):
self
.
visit
(
node
.
lhs
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visit
(
node
.
rhs
)
return
node
def
visit_CascadedAssignmentNode
(
self
,
node
):
for
lhs
in
node
.
lhs_list
:
lhs_ref_id
=
self
.
reference_identifier
(
lhs
)
if
self
.
rlocked
[
lhs_ref_id
]
>
0
or
self
.
wlocked
[
lhs_ref_id
]
>
0
:
# Disallow re-binding a locked name
error
(
lhs
.
pos
,
"Assigning to a locked cypclass reference"
)
return
node
for
lhs
in
node
.
lhs_list
:
node
.
rhs
=
self
.
lockcheck_if_subscript_rhs
(
lhs
,
node
.
rhs
)
with
self
.
accesscontext
(
writing
=
True
):
for
lhs
in
node
.
lhs_list
:
self
.
visit
(
lhs
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visit
(
node
.
rhs
)
return
node
def
visit_WithTargetAssignmentStatNode
(
self
,
node
):
target_id
=
self
.
reference_identifier
(
node
.
lhs
)
if
self
.
rlocked
[
target_id
]
>
0
or
self
.
wlocked
[
target_id
]
>
0
:
# Disallow re-binding a locked name
error
(
node
.
lhs
.
pos
,
"With expression target is a locked cypclass reference"
)
return
node
node
.
rhs
=
self
.
lockcheck_if_subscript_rhs
(
node
.
lhs
,
node
.
rhs
)
with
self
.
accesscontext
(
writing
=
True
):
self
.
visit
(
node
.
lhs
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visit
(
node
.
rhs
)
return
node
def
visit__ForInStatNode
(
self
,
node
):
target_id
=
self
.
reference_identifier
(
node
.
target
)
if
self
.
rlocked
[
target_id
]
>
0
or
self
.
wlocked
[
target_id
]
>
0
:
# Disallow re-binding a locked name
error
(
node
.
target
.
pos
,
"For-Loop target is a locked cypclass reference"
)
return
node
node
.
item
=
self
.
lockcheck_if_subscript_rhs
(
node
.
target
,
node
.
item
)
with
self
.
accesscontext
(
writing
=
True
):
self
.
visit
(
node
.
target
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visit
(
node
.
item
)
self
.
visit
(
node
.
body
)
self
.
visit
(
node
.
iterator
)
if
node
.
else_clause
:
self
.
visit
(
node
.
else_clause
)
return
node
def
visit_ExceptClauseNode
(
self
,
node
):
if
not
node
.
target
:
self
.
visitchildren
(
node
)
else
:
target_id
=
self
.
reference_identifier
(
node
.
target
)
if
self
.
rlocked
[
target_id
]
>
0
or
self
.
wlocked
[
target_id
]
>
0
:
# Disallow re-binding a locked name
error
(
node
.
target
.
pos
,
"Except clause target is a locked cypclass reference"
)
return
node
with
self
.
accesscontext
(
writing
=
True
):
self
.
visit
(
node
.
target
)
for
p
in
node
.
pattern
:
self
.
visit
(
p
)
self
.
visit
(
node
.
body
)
return
node
def
visit_AttributeNode
(
self
,
node
):
if
node
.
obj
.
type
and
node
.
obj
.
type
.
is_cyp_class
:
if
node
.
is_called
and
node
.
type
.
is_cfunction
:
if
not
node
.
type
.
is_static_method
:
node
.
obj
=
self
.
lockcheck_written_or_read
(
node
.
obj
,
reading
=
node
.
type
.
is_const_method
)
else
:
node
.
obj
=
self
.
lockcheck_on_context
(
node
.
obj
)
with
self
.
accesscontext
(
reading
=
True
):
obj
=
node
.
obj
objtype
=
obj
.
type
nodetype
=
node
.
type
if
objtype
is
None
or
nodetype
is
None
:
self
.
visitchildren
(
node
)
return
node
def
visit_SimpleCallNode
(
self
,
node
):
if
node
.
type
.
is_error
:
return
node
func_type
=
node
.
function_type
()
if
func_type
.
is_cfunction
:
formal_nargs
=
len
(
func_type
.
args
)
actual_nargs
=
len
(
node
.
args
)
for
i
,
formal_arg
,
actual_arg
in
zip
(
range
(
actual_nargs
),
func_type
.
args
,
node
.
args
):
if
formal_arg
.
type
.
is_cyp_class
and
actual_arg
.
type
.
is_cyp_class
:
node
.
args
[
i
]
=
self
.
lockcheck_written_or_read
(
actual_arg
,
reading
=
formal_arg
.
type
.
is_const_cyp_class
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_CoerceFromCallable
(
self
,
node
):
if
node
.
arg
.
type
.
is_cyp_class
:
node
.
arg
=
self
.
lockcheck_written_or_read
(
node
.
arg
,
reading
=
node
.
type
.
is_const_method
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_IndexNode
(
self
,
node
):
if
node
.
base
.
type
and
node
.
base
.
type
.
is_cyp_class
:
func_entry
=
None
if
self
.
deleting
:
func_entry
=
node
.
base
.
type
.
scope
.
lookup
(
"__delitem__"
)
elif
self
.
writing
:
func_entry
=
node
.
base
.
type
.
scope
.
lookup
(
"__setitem__"
)
elif
self
.
reading
:
func_entry
=
node
.
base
.
type
.
scope
.
lookup
(
"__getitem__"
)
if
func_entry
:
func_type
=
func_entry
.
type
node
.
base
=
self
.
lockcheck_written_or_read
(
node
.
base
,
reading
=
func_type
.
is_const_method
)
if
len
(
func_type
.
args
):
if
func_type
.
args
[
0
].
type
.
is_cyp_class
:
node
.
index
=
self
.
lockcheck_written_or_read
(
node
.
index
,
reading
=
func_type
.
args
[
0
].
type
.
is_const_cyp_class
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
_visit_binop
(
self
,
node
,
func_type
):
if
func_type
is
not
None
:
if
node
.
operand1
.
type
.
is_cyp_class
and
len
(
func_type
.
args
)
==
1
:
node
.
operand1
=
self
.
lockcheck_written_or_read
(
node
.
operand1
,
reading
=
func_type
.
is_const_method
)
arg_type
=
func_type
.
args
[
0
].
type
if
arg_type
.
is_cyp_class
:
node
.
operand2
=
self
.
lockcheck_written_or_read
(
node
.
operand2
,
reading
=
arg_type
.
is_const_cyp_class
)
elif
len
(
func_type
.
args
)
==
2
:
arg1_type
=
func_type
.
args
[
0
].
type
if
arg1_type
.
is_cyp_class
:
node
.
operand1
=
self
.
lockcheck_written_or_read
(
node
.
operand1
,
reading
=
arg1_type
.
is_const_cyp_class
)
arg2_type
=
func_type
.
args
[
1
].
type
if
arg2_type
.
is_cyp_class
:
node
.
operand2
=
self
.
lockcheck_written_or_read
(
node
.
operand2
,
reading
=
arg2_type
.
is_const_cyp_class
)
def
visit_BinopNode
(
self
,
node
):
func_type
=
node
.
op_func_type
self
.
_visit_binop
(
node
,
func_type
)
with
self
.
accesscontext
(
reading
=
True
):
field_access
=
node
.
entry
and
not
node
.
entry
.
is_type
and
not
nodetype
.
is_cfunction
method_call
=
nodetype
.
is_cfunction
and
not
nodetype
.
is_static_method
and
node
.
is_called
# The 'const' annotation is only sufficient to infer
# that the receiver object will only be read, not that
# __all__ the reachable subobjects will only be read.
locally_writing
=
(
field_access
and
node
.
is_target
)
or
method_call
if
objtype
.
is_qualified_cyp_class
and
objtype
.
qualifier
==
'locked'
:
old_writing
=
self
.
writing
self
.
writing
=
locally_writing
self
.
visitchildren
(
node
)
return
node
def
visit_PrimaryCmpNode
(
self
,
node
):
func_type
=
node
.
cmp_func_type
self
.
_visit_binop
(
node
,
func_type
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_InPlaceAssignmentNode
(
self
,
node
):
# operator = "operator%s="% node.operator
# if node.lhs.type.is_cyp_class:
# TODO: get operator function type and treat it like a binop with lhs and rhs
with
self
.
accesscontext
(
reading
=
True
,
writing
=
True
):
self
.
visit
(
node
.
lhs
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visit
(
node
.
rhs
)
return
node
def
_visit_unop
(
self
,
node
,
func_type
):
if
func_type
is
not
None
:
if
node
.
operand
.
type
.
is_cyp_class
and
len
(
func_type
.
args
)
==
0
:
node
.
operand
=
self
.
lockcheck_written_or_read
(
node
.
operand
,
reading
=
func_type
.
is_const_method
)
def
visit_UnopNode
(
self
,
node
):
func_type
=
node
.
op_func_type
self
.
_visit_unop
(
node
,
func_type
)
with
self
.
accesscontext
(
reading
=
True
):
self
.
visitchildren
(
node
)
return
node
def
visit_TypecastNode
(
self
,
node
):
func_type
=
node
.
op_func_type
self
.
_visit_unop
(
node
,
func_type
)
with
self
.
accesscontext
(
reading
=
True
):
if
field_access
or
method_call
:
node
.
obj
=
ExprNodes
.
CoerceToLockedNode
(
node
.
obj
,
exclusive
=
self
.
writing
)
self
.
writing
=
old_writing
elif
objtype
.
is_cyp_class
:
old_writing
=
self
.
writing
self
.
writing
|=
locally_writing
self
.
visitchildren
(
node
)
self
.
writing
=
old_writing
return
node
Cython/Compiler/ExprNodes.py
View file @
a80ce036
...
...
@@ -7310,6 +7310,7 @@ class AttributeNode(ExprNode):
return
node
def
analyse_types
(
self
,
env
,
target
=
0
):
self
.
is_target
=
target
self
.
initialized_check
=
env
.
directives
[
'initializedcheck'
]
node
=
self
.
analyse_as_cimported_attribute_node
(
env
,
target
)
if
node
is
None
and
not
target
:
...
...
@@ -14261,10 +14262,10 @@ class CoerceToTempNode(CoercionNode):
class
CoerceToLockedNode
(
CoercionNode
):
# This node is used to lock a node of cypclass type around the evaluation of its subexpressions.
#
rlock_only
boolean
#
exclusive
boolean
def
__init__
(
self
,
arg
,
env
=
None
,
rlock_only
=
Fals
e
):
self
.
rlock_only
=
rlock_only
def
__init__
(
self
,
arg
,
env
=
None
,
exclusive
=
Tru
e
):
self
.
exclusive
=
exclusive
self
.
type
=
arg
.
type
arg
=
arg
.
coerce_to_temp
(
env
)
arg
.
postpone_subexpr_disposal
=
True
...
...
@@ -14295,13 +14296,13 @@ class CoerceToLockedNode(CoercionNode):
# Create a scope to use scope bound resource management (RAII).
code
.
putln
(
"{"
)
# Since each lock guard has its o
nw
scope,
# Since each lock guard has its o
wn
scope,
# a prefix is enough to prevent name collisions.
guard_code
=
"%sguard"
%
Naming
.
cypclass_lock_guard_prefix
if
self
.
rlock_only
:
code
.
putln
(
"Cy_rlock_guard %s(%s, %s);"
%
(
guard_code
,
self
.
result
(),
context
))
else
:
if
self
.
exclusive
:
code
.
putln
(
"Cy_wlock_guard %s(%s, %s);"
%
(
guard_code
,
self
.
result
(),
context
))
else
:
code
.
putln
(
"Cy_rlock_guard %s(%s, %s);"
%
(
guard_code
,
self
.
result
(),
context
))
def
generate_disposal_code
(
self
,
code
):
# Close the scope to release the lock.
...
...
Cython/Compiler/ModuleNode.py
View file @
a80ce036
...
...
@@ -1209,13 +1209,6 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
reified_call_args_list
.
append
(
Naming
.
optional_args_cname
)
# Locking CyObjects
# Here we completely ignore the lock mode (nolock/checklock/autolock)
# because the mode is used for direct calls, when the user have the possibility
# to manually lock or let the compiler handle it.
# Here, the user cannot lock manually, so we're taking the lock automatically.
#put_cypclass_op_on_narg_optarg(lambda arg: "Cy_RLOCK" if arg.type.is_const else "Cy_WLOCK",
# reified_function_entry.type, Naming.optional_args_cname, code)
func_type
=
reified_function_entry
.
type
opt_arg_name
=
Naming
.
optional_args_cname
trylock_result
=
"trylock_result"
...
...
Cython/Compiler/Nodes.py
View file @
a80ce036
...
...
@@ -1559,7 +1559,6 @@ class CppClassNode(CStructOrUnionDefNode, BlockNode):
# templates [(string, bool)] or None
# decorators [DecoratorNode] or None
# cypclass boolean
# lock_mode 'nolock', 'checklock', 'autolock', or None
# activable boolean
decorators
=
None
...
...
@@ -1584,7 +1583,7 @@ class CppClassNode(CStructOrUnionDefNode, BlockNode):
self
.
entry
=
env
.
declare_cpp_class
(
self
.
name
,
None
,
self
.
pos
,
self
.
cname
,
base_classes
=
[],
visibility
=
self
.
visibility
,
templates
=
template_types
,
cypclass
=
self
.
cypclass
,
lock_mode
=
self
.
lock_mode
,
activable
=
self
.
activable
)
cypclass
=
self
.
cypclass
,
activable
=
self
.
activable
)
def
analyse_declarations
(
self
,
env
):
if
self
.
templates
is
None
:
...
...
@@ -1642,7 +1641,7 @@ class CppClassNode(CStructOrUnionDefNode, BlockNode):
self
.
entry
=
env
.
declare_cpp_class
(
self
.
name
,
scope
,
self
.
pos
,
self
.
cname
,
base_class_types
,
visibility
=
self
.
visibility
,
templates
=
template_types
,
cypclass
=
self
.
cypclass
,
lock_mode
=
self
.
lock_mode
,
activable
=
self
.
activable
)
cypclass
=
self
.
cypclass
,
activable
=
self
.
activable
)
if
self
.
entry
is
None
:
return
self
.
entry
.
is_cpp_class
=
1
...
...
@@ -8615,8 +8614,11 @@ class LockCypclassNode(StatNode):
self
.
obj
.
analyse_declarations
(
env
)
def
analyse_expressions
(
self
,
env
):
self
.
obj
=
self
.
obj
.
analyse_types
(
env
)
self
.
obj
=
obj
=
self
.
obj
.
analyse_types
(
env
)
self
.
body
=
self
.
body
.
analyse_expressions
(
env
)
if
not
obj
.
type
.
is_cyp_class
:
error
(
obj
.
pos
,
"Locking non-cypclass reference"
)
return
self
return
self
def
generate_execution_code
(
self
,
code
):
...
...
Cython/Compiler/Parsing.py
View file @
a80ce036
...
...
@@ -2544,7 +2544,7 @@ def p_c_simple_base_type(s, self_flag, nonempty, templates = None):
base_type
=
base_type
,
is_const
=
is_const
,
is_volatile
=
is_volatile
)
# Handle cypclass qualifiers
if
s
.
sy
==
'IDENT'
and
s
.
systring
in
(
'active'
,
'iso'
):
if
s
.
sy
==
'IDENT'
and
s
.
systring
in
(
'active'
,
'iso'
,
'locked'
):
qualifier
=
s
.
systring
s
.
next
()
base_type
=
p_c_base_type
(
s
,
self_flag
=
self_flag
,
nonempty
=
nonempty
,
templates
=
templates
)
...
...
@@ -3864,10 +3864,8 @@ def p_cpp_class_definition(s, pos, ctx):
if
s
.
sy
==
'['
:
error
(
s
.
position
(),
"Name options not allowed for C++ class"
)
nogil
=
p_nogil
(
s
)
or
cypclass
lock_mode
=
None
activable
=
False
if
cypclass
:
lock_mode
=
p_cypclass_lock_mode
(
s
)
activable
=
p_cypclass_activable
(
s
)
if
s
.
sy
==
':'
:
s
.
next
()
...
...
@@ -3897,7 +3895,7 @@ def p_cpp_class_definition(s, pos, ctx):
visibility
=
ctx
.
visibility
,
in_pxd
=
ctx
.
level
==
'module_pxd'
,
attributes
=
attributes
,
templates
=
templates
,
cypclass
=
cypclass
,
lock_mode
=
lock_mode
,
activable
=
activable
)
templates
=
templates
,
cypclass
=
cypclass
,
activable
=
activable
)
def
p_cpp_class_attribute
(
s
,
ctx
):
decorators
=
None
...
...
@@ -3923,14 +3921,6 @@ def p_cpp_class_attribute(s, ctx):
node
.
decorators
=
decorators
return
node
def
p_cypclass_lock_mode
(
s
):
if
s
.
sy
==
'IDENT'
and
s
.
systring
in
(
'nolock'
,
'checklock'
,
'autolock'
):
mode
=
s
.
systring
s
.
next
()
return
mode
else
:
return
None
def
p_cypclass_activable
(
s
):
if
s
.
sy
==
'IDENT'
and
s
.
systring
==
'activable'
:
s
.
next
()
...
...
Cython/Compiler/PyrexTypes.py
View file @
a80ce036
...
...
@@ -4243,7 +4243,7 @@ class CppClassType(CType):
CppClassType
(
self
.
name
,
None
,
self
.
cname
,
[],
template_values
,
template_type
=
self
)
\
if
not
self
.
is_cyp_class
else
\
CypClassType
(
self
.
name
,
None
,
self
.
cname
,
[],
template_values
,
template_type
=
self
,
lock_mode
=
self
.
lock_mode
,
activable
=
self
.
activable
)
activable
=
self
.
activable
)
# Need to do these *after* self.specializations[key] is set
# to avoid infinite recursion on circular references.
specialized
.
base_classes
=
[
b
.
specialize
(
values
)
for
b
in
self
.
base_classes
]
...
...
@@ -4538,7 +4538,6 @@ def compute_mro_generic(cls):
return
mro_C3_merge
(
inputs
)
class
CypClassType
(
CppClassType
):
# lock_mode string (tri-state: "nolock"/"checklock"/"autolock")
# _mro [CppClassType] or None the Method Resolution Order of this cypclass according to Python
# support_wrapper boolean whether this cypclass will be wrapped
# wrapper_type PyExtensionType or None the type of the cclass wrapper
...
...
@@ -4547,9 +4546,8 @@ class CypClassType(CppClassType):
to_py_function
=
None
from_py_function
=
None
def
__init__
(
self
,
name
,
scope
,
cname
,
base_classes
,
templates
=
None
,
template_type
=
None
,
nogil
=
0
,
lock_mode
=
None
,
activable
=
False
):
def
__init__
(
self
,
name
,
scope
,
cname
,
base_classes
,
templates
=
None
,
template_type
=
None
,
nogil
=
0
,
activable
=
False
):
CppClassType
.
__init__
(
self
,
name
,
scope
,
cname
,
base_classes
,
templates
,
template_type
,
nogil
)
self
.
lock_mode
=
lock_mode
if
lock_mode
else
"autolock"
self
.
activable
=
activable
self
.
_mro
=
None
self
.
support_wrapper
=
False
...
...
@@ -4817,6 +4815,7 @@ class QualifiedCypclassType(BaseType):
'iso'
:
(
'iso~'
,),
'iso~'
:
(),
'iso&'
:
(
'iso~'
,),
'locked'
:
(
'locked'
,
'iso~'
),
}
def
__init__
(
self
,
base_type
,
qualifier
):
...
...
Cython/Compiler/Symtab.py
View file @
a80ce036
...
...
@@ -781,7 +781,7 @@ class Scope(object):
def
declare_cpp_class
(
self
,
name
,
scope
,
pos
,
cname
=
None
,
base_classes
=
(),
visibility
=
'extern'
,
templates
=
None
,
cypclass
=
0
,
lock_mode
=
None
,
activable
=
False
):
visibility
=
'extern'
,
templates
=
None
,
cypclass
=
0
,
activable
=
False
):
if
cname
is
None
:
if
self
.
in_cinclude
or
(
visibility
!=
'private'
):
cname
=
name
...
...
@@ -792,7 +792,7 @@ class Scope(object):
if
not
entry
:
if
cypclass
:
type
=
PyrexTypes
.
CypClassType
(
name
,
scope
,
cname
,
base_classes
,
templates
=
templates
,
lock_mode
=
lock_mode
,
activable
=
activable
)
name
,
scope
,
cname
,
base_classes
,
templates
=
templates
,
activable
=
activable
)
else
:
type
=
PyrexTypes
.
CppClassType
(
name
,
scope
,
cname
,
base_classes
,
templates
=
templates
)
...
...
@@ -3288,6 +3288,8 @@ def qualified_cypclass_scope(base_type_scope, qualifier):
return
ActiveCypclassScope
(
base_type_scope
)
elif
qualifier
.
startswith
(
'iso'
):
return
IsoCypclassScope
(
base_type_scope
)
elif
qualifier
==
'locked'
:
return
IsoCypclassScope
(
base_type_scope
,
'locked'
)
else
:
return
QualifiedCypclassScope
(
base_type_scope
,
qualifier
)
...
...
tests/errors/cypclass_lock_error.pyx
deleted
100644 → 0
View file @
a66ffc46
# mode: error
# tag: cpp, cpp11, pthread
# cython: experimental_cpp_class_def=True, language_level=2
cdef
cypclass
A
checklock
:
int
a
int
getter
(
const
self
):
return
self
.
a
void
setter
(
self
,
int
a
):
self
.
a
=
a
cdef
void
take_write_locked
(
A
obj
):
pass
cdef
int
take_read_locked
(
const
A
obj
):
return
3
def
incorrect_locks
():
obj
=
A
()
obj
.
a
=
3
obj
.
getter
()
with
rlocked
obj
:
obj
.
setter
(
42
)
take_write_locked
(
obj
)
obj
.
a
take_read_locked
(
obj
)
cdef
A
global_cyobject
cdef
void
global_lock_taking
():
with
wlocked
global_cyobject
:
global_cyobject
.
setter
(
global_cyobject
.
getter
()
+
1
)
_ERRORS
=
u"""
20:4: Reference 'obj' is not correctly locked in this expression (write lock required)
21:4: Reference 'obj' is not correctly locked in this expression (read lock required)
23:8: Reference 'obj' is not correctly locked in this expression (write lock required)
24:26: Reference 'obj' is not correctly locked in this expression (write lock required)
25:4: Reference 'obj' is not correctly locked in this expression (read lock required)
26:21: Reference 'obj' is not correctly locked in this expression (read lock required)
32:17: Can only lock local variables or arguments
"""
tests/run/cypclass_acthon.pyx
View file @
a80ce036
...
...
@@ -14,7 +14,7 @@ cdef extern from "<semaphore.h>" nogil:
int
sem_destroy
(
sem_t
*
sem
)
cdef
cypclass
BasicQueue
(
ActhonQueueInterface
)
checklock
:
cdef
cypclass
BasicQueue
(
ActhonQueueInterface
):
message_queue_t
*
_queue
__init__
(
self
):
...
...
@@ -53,7 +53,7 @@ cdef cypclass BasicQueue(ActhonQueueInterface) checklock:
# Don't forget to incref to avoid premature deallocation
return
one_message_processed
cdef
cypclass
NoneResult
(
ActhonResultInterface
)
checklock
:
cdef
cypclass
NoneResult
(
ActhonResultInterface
):
void
pushVoidStarResult
(
self
,
void
*
result
):
pass
void
pushIntResult
(
self
,
int
result
):
...
...
@@ -63,7 +63,7 @@ cdef cypclass NoneResult(ActhonResultInterface) checklock:
int
getIntResult
(
const
self
):
return
0
cdef
cypclass
WaitResult
(
ActhonResultInterface
)
checklock
:
cdef
cypclass
WaitResult
(
ActhonResultInterface
):
union
result_t
:
int
int_val
void
*
ptr
...
...
@@ -104,7 +104,7 @@ cdef cypclass WaitResult(ActhonResultInterface) checklock:
res
=
self
.
_getRawResult
()
return
res
.
int_val
cdef
cypclass
ActivityCounterSync
(
ActhonSyncInterface
)
checklock
:
cdef
cypclass
ActivityCounterSync
(
ActhonSyncInterface
):
int
count
ActivityCounterSync
previous_sync
...
...
@@ -129,7 +129,7 @@ cdef cypclass ActivityCounterSync(ActhonSyncInterface) checklock:
res
=
prev_sync
.
isCompleted
()
return
res
cdef
cypclass
A
checklock
activable
:
cdef
cypclass
A
activable
:
int
a
__init__
(
self
):
self
.
a
=
0
...
...
@@ -148,19 +148,17 @@ def test_acthon_chain(n):
cdef
ActhonResultInterface
res
cdef
ActhonQueueInterface
queue
sync1
=
ActivityCounterSync
()
with
wlocked
sync1
:
after_sync1
=
ActivityCounterSync
(
sync1
)
obj
=
A
()
with
wlocked
obj
:
obj_actor
=
activate
(
obj
)
with
wlocked
obj_actor
,
wlocked
sync1
,
wlocked
after_sync1
:
# Pushing things in the queue
obj_actor
.
setter
(
sync1
,
n
)
res
=
obj_actor
.
getter
(
after_sync1
)
# Processing the queue
with
rlocked
obj
:
queue
=
obj
.
_active_queue_class
with
wlocked
queue
:
while
not
queue
.
is_empty
():
queue
.
activate
()
print
<
int
>
res
tests/run/cypclass_exception_refcount.pyx
View file @
a80ce036
...
...
@@ -2,7 +2,7 @@
# tag: cpp, cpp11
# cython: experimental_cpp_class_def=True, language_level=2
cdef
cypclass
Refcounted
nolock
:
cdef
cypclass
Refcounted
:
pass
cdef
int
raises
(
Refcounted
r
)
except
0
:
...
...
tests/run/cypclass_inplace_operator_inference.pyx
View file @
a80ce036
...
...
@@ -2,7 +2,7 @@
# tag: cpp, cpp11
# cython: experimental_cpp_class_def=True, language_level=2
cdef
cypclass
A
nolock
:
cdef
cypclass
A
:
int
val
__init__
(
self
,
int
a
):
self
.
val
=
a
...
...
@@ -10,7 +10,7 @@ cdef cypclass A nolock:
A
__iadd__
(
self
,
A
other
):
self
.
val
+=
(
other
.
val
+
1
)
cdef
cypclass
B
(
A
)
nolock
:
cdef
cypclass
B
(
A
):
B
__iadd__
(
self
,
A
other
):
self
.
val
+=
(
other
.
val
+
10
)
...
...
tests/run/cypclass_lock.pyx
deleted
100644 → 0
View file @
a66ffc46
# mode: run
# tag: cpp, cpp11, pthread
# cython: experimental_cpp_class_def=True, language_level=2
cdef
cypclass
A
checklock
:
int
a
__init__
(
self
):
self
.
a
=
0
int
getter
(
const
self
):
return
self
.
a
void
setter
(
self
,
int
a
):
self
.
a
=
a
def
test_basic_locking
():
"""
>>> test_basic_locking()
0
"""
obj
=
A
()
with
rlocked
obj
:
print
obj
.
getter
()
cdef
argument_recursivity
(
A
obj
,
int
arg
):
if
arg
>
0
:
obj
.
setter
(
obj
.
getter
()
+
1
)
argument_recursivity
(
obj
,
arg
-
1
)
def
test_argument_recursivity
(
n
):
"""
>>> test_argument_recursivity(42)
42
"""
obj
=
A
()
with
wlocked
obj
:
argument_recursivity
(
obj
,
n
)
print
obj
.
a
cdef
cypclass
Container
:
A
object
__init__
(
self
):
self
.
object
=
A
()
def
test_lock_traversal
(
n
):
"""
>>> test_lock_traversal(42)
42
"""
container
=
Container
()
with
rlocked
container
:
contained
=
container
.
object
with
wlocked
contained
:
argument_recursivity
(
contained
,
n
)
print
contained
.
getter
()
tests/run/cypclass_nested_classes.pyx
View file @
a80ce036
...
...
@@ -2,10 +2,10 @@
# tag: cpp, cpp11
# cython: experimental_cpp_class_def=True, language_level=2
cdef
cypclass
A
nolock
:
cdef
cypclass
A
:
int
a
cypclass
B
nolock
:
cypclass
B
:
int
b
__init__
(
self
,
int
b
):
self
.
b
=
b
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment