Commit a80ce036 authored by Xavier Thompson's avatar Xavier Thompson

Introduce 'locked' qualifier and refactor locking

parent a66ffc46
......@@ -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
......
......@@ -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.
Acquire cypclass locks where required.
"""
class StackLock:
"""
Context manager for tracking nested locks.
"""
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):
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):
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_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
......@@ -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=False):
self.rlock_only = rlock_only
def __init__(self, arg, env=None, exclusive=True):
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 onw scope,
# Since each lock guard has its own 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.
......
......@@ -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"
......
......@@ -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):
......
......@@ -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()
......
......@@ -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):
......
......@@ -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)
......
# 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
"""
......@@ -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)
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)
obj_actor = activate(obj)
# 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()
queue = obj._active_queue_class
while not queue.is_empty():
queue.activate()
print <int> res
......@@ -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:
......
......@@ -2,18 +2,18 @@
# 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
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)
int is_inferred_as_B(self):
return 1
......@@ -30,7 +30,7 @@ def test_inplace_operator_inference():
a += b # should add 1
# before it being fixed, 'b += a' where 'a' is of type A caused 'b' to be inferred as type A instead of B.
b += a # should add 10
# since all cypclass methods are virtual, 'b' being erroneously inferred as type A would cause a compilation error
......
# 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()
......@@ -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
......@@ -18,7 +18,7 @@ cdef cypclass A nolock:
__init__(self, int a, int b):
self.a = a
self.b = B(b)
int foo(self):
return self.a + self.b.foo()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment