diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs index ae4f57f6d1..7920c3b245 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseInstanceState.cs @@ -121,7 +121,9 @@ public string GetDisplayPath(int maxLength, char seperator) { string name = GetNonNullText(this); - if (m_parent == null) + NodeState stateParent = m_parent; + + if (stateParent == null) { return name; } @@ -130,7 +132,7 @@ public string GetDisplayPath(int maxLength, char seperator) if (maxLength > 2) { - NodeState parent = m_parent; + NodeState parent = stateParent; List names = new List(); while (parent != null) @@ -158,7 +160,7 @@ public string GetDisplayPath(int maxLength, char seperator) } } - buffer.Append(GetNonNullText(m_parent)); + buffer.Append(GetNonNullText(stateParent)); buffer.Append(seperator); buffer.Append(name); @@ -272,10 +274,8 @@ public override void ReportEvent(ISystemContext context, IFilterTarget e) base.ReportEvent(context, e); // recusively notify the parent. - if (m_parent != null) - { - m_parent.ReportEvent(context, e); - } + m_parent?.ReportEvent(context, e); + } /// @@ -691,29 +691,37 @@ protected override void PopulateBrowser(ISystemContext context, NodeBrowser brow { base.PopulateBrowser(context, browser); - if (!NodeId.IsNull(m_typeDefinitionId) && IsObjectOrVariable) + NodeId typeDefinitionId = m_typeDefinitionId; + + if (!NodeId.IsNull(typeDefinitionId) && IsObjectOrVariable) { if (browser.IsRequired(ReferenceTypeIds.HasTypeDefinition, false)) { - browser.Add(ReferenceTypeIds.HasTypeDefinition, false, m_typeDefinitionId); + browser.Add(ReferenceTypeIds.HasTypeDefinition, false, typeDefinitionId); } } - if (!NodeId.IsNull(m_modellingRuleId)) + NodeId modellingRuleId = m_modellingRuleId; + + if (!NodeId.IsNull(modellingRuleId)) { if (browser.IsRequired(ReferenceTypeIds.HasModellingRule, false)) { - browser.Add(ReferenceTypeIds.HasModellingRule, false, m_modellingRuleId); + browser.Add(ReferenceTypeIds.HasModellingRule, false, modellingRuleId); } } - if (m_parent != null) + NodeState parent = m_parent; + + if (parent != null) { - if (!NodeId.IsNull(m_referenceTypeId)) + NodeId referenceTypeId = this.m_referenceTypeId; + + if (!NodeId.IsNull(referenceTypeId)) { - if (browser.IsRequired(m_referenceTypeId, true)) + if (browser.IsRequired(referenceTypeId, true)) { - browser.Add(m_referenceTypeId, true, m_parent); + browser.Add(referenceTypeId, true, parent); } } } diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs index d501f8ec30..e3a341e090 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseObjectState.cs @@ -269,9 +269,11 @@ protected override ServiceResult ReadNonValueAttribute( { byte eventNotifier = m_eventNotifier; - if (OnReadEventNotifier != null) + NodeAttributeEventHandler readEventNotifier = OnReadEventNotifier; + + if (readEventNotifier != null) { - result = OnReadEventNotifier(context, this, ref eventNotifier); + result = readEventNotifier(context, this, ref eventNotifier); } if (ServiceResult.IsGood(result)) @@ -316,9 +318,11 @@ protected override ServiceResult WriteNonValueAttribute( byte eventNotifier = eventNotifierRef.Value; - if (OnWriteEventNotifier != null) + NodeAttributeEventHandler writeEventNotifier = OnWriteEventNotifier; + + if (writeEventNotifier != null) { - result = OnWriteEventNotifier(context, this, ref eventNotifier); + result = writeEventNotifier(context, this, ref eventNotifier); } if (ServiceResult.IsGood(result)) diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs index dfd40b936e..de49c6c6f6 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs @@ -302,9 +302,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool isAbstract = m_isAbstract; - if (OnReadIsAbstract != null) + NodeAttributeEventHandler onReadIsAbstract = OnReadIsAbstract; + + if (onReadIsAbstract != null) { - result = OnReadIsAbstract(context, this, ref isAbstract); + result = onReadIsAbstract(context, this, ref isAbstract); } if (ServiceResult.IsGood(result)) @@ -349,9 +351,11 @@ protected override ServiceResult WriteNonValueAttribute( bool isAbstract = isAbstractRef.Value; - if (OnWriteIsAbstract != null) + NodeAttributeEventHandler onWriteIsAbstract = OnWriteIsAbstract; + + if (onWriteIsAbstract != null) { - result = OnWriteIsAbstract(context, this, ref isAbstract); + result = onWriteIsAbstract(context, this, ref isAbstract); } if (ServiceResult.IsGood(result)) @@ -377,20 +381,24 @@ protected override void PopulateBrowser(ISystemContext context, NodeBrowser brow { base.PopulateBrowser(context, browser); - if (!NodeId.IsNull(m_superTypeId)) + NodeId superTypeId = m_superTypeId; + + if (!NodeId.IsNull(superTypeId)) { if (browser.IsRequired(ReferenceTypeIds.HasSubtype, true)) { - browser.Add(ReferenceTypeIds.HasSubtype, true, m_superTypeId); + browser.Add(ReferenceTypeIds.HasSubtype, true, superTypeId); } } + NodeId nodeId = this.NodeId; + // use the type table to find the subtypes. - if (context.TypeTable != null && this.NodeId != null) + if (context.TypeTable != null && nodeId != null) { if (browser.IsRequired(ReferenceTypeIds.HasSubtype, false)) { - IList subtypeIds = context.TypeTable.FindSubTypes(this.NodeId); + IList subtypeIds = context.TypeTable.FindSubTypes(nodeId); for (int ii = 0; ii < subtypeIds.Count; ii++) { diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs index 8c3dbebd3f..077af2c8bf 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseVariableState.cs @@ -887,9 +887,11 @@ protected override void Export(ISystemContext context, Node node) variableNode.ValueRank = this.ValueRank; variableNode.ArrayDimensions = null; - if (this.ArrayDimensions != null) + ReadOnlyList arrayDimensions = this.ArrayDimensions; + + if (arrayDimensions != null) { - variableNode.ArrayDimensions = new UInt32Collection(this.ArrayDimensions); + variableNode.ArrayDimensions = new UInt32Collection(arrayDimensions); } variableNode.AccessLevel = this.AccessLevel; @@ -1341,9 +1343,11 @@ protected override ServiceResult ReadNonValueAttribute( { NodeId dataType = m_dataType; - if (OnReadDataType != null) + NodeAttributeEventHandler onReadDataType = OnReadDataType; + + if (onReadDataType != null) { - result = OnReadDataType(context, this, ref dataType); + result = onReadDataType(context, this, ref dataType); } if (ServiceResult.IsGood(result)) @@ -1358,9 +1362,11 @@ protected override ServiceResult ReadNonValueAttribute( { int valueRank = m_valueRank; - if (OnReadValueRank != null) + NodeAttributeEventHandler onReadValueRank = OnReadValueRank; + + if (onReadValueRank != null) { - result = OnReadValueRank(context, this, ref valueRank); + result = onReadValueRank(context, this, ref valueRank); } if (ServiceResult.IsGood(result)) @@ -1375,9 +1381,11 @@ protected override ServiceResult ReadNonValueAttribute( { IList arrayDimensions = m_arrayDimensions; - if (OnReadArrayDimensions != null) + NodeAttributeEventHandler> onReadArrayDimensions = OnReadArrayDimensions; + + if (onReadArrayDimensions != null) { - result = OnReadArrayDimensions(context, this, ref arrayDimensions); + result = onReadArrayDimensions(context, this, ref arrayDimensions); } if (ServiceResult.IsGood(result)) @@ -1392,9 +1400,11 @@ protected override ServiceResult ReadNonValueAttribute( { byte accessLevel = AccessLevel; - if (OnReadAccessLevel != null) + NodeAttributeEventHandler onReadAccessLevel = OnReadAccessLevel; + + if (onReadAccessLevel != null) { - result = OnReadAccessLevel(context, this, ref accessLevel); + result = onReadAccessLevel(context, this, ref accessLevel); } if (ServiceResult.IsGood(result)) @@ -1409,9 +1419,11 @@ protected override ServiceResult ReadNonValueAttribute( { uint accessLevelEx = m_accessLevel; - if (OnReadAccessLevelEx != null) + NodeAttributeEventHandler onReadAccessLevelEx = OnReadAccessLevelEx; + + if (onReadAccessLevelEx != null) { - result = OnReadAccessLevelEx(context, this, ref accessLevelEx); + result = onReadAccessLevelEx(context, this, ref accessLevelEx); } if (ServiceResult.IsGood(result)) @@ -1426,9 +1438,11 @@ protected override ServiceResult ReadNonValueAttribute( { byte userAccessLevel = m_userAccessLevel; - if (OnReadUserAccessLevel != null) + NodeAttributeEventHandler onReadUserAccessLevel = OnReadUserAccessLevel; + + if (onReadUserAccessLevel != null) { - result = OnReadUserAccessLevel(context, this, ref userAccessLevel); + result = onReadUserAccessLevel(context, this, ref userAccessLevel); } if (ServiceResult.IsGood(result)) @@ -1443,9 +1457,11 @@ protected override ServiceResult ReadNonValueAttribute( { double minimumSamplingInterval = m_minimumSamplingInterval; - if (OnReadMinimumSamplingInterval != null) + NodeAttributeEventHandler onReadMinimumSamplingInterval = OnReadMinimumSamplingInterval; + + if (onReadMinimumSamplingInterval != null) { - result = OnReadMinimumSamplingInterval(context, this, ref minimumSamplingInterval); + result = onReadMinimumSamplingInterval(context, this, ref minimumSamplingInterval); } if (ServiceResult.IsGood(result)) @@ -1460,9 +1476,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool historizing = m_historizing; - if (OnReadHistorizing != null) + NodeAttributeEventHandler onReadHistorizing = OnReadHistorizing; + + if (onReadHistorizing != null) { - result = OnReadHistorizing(context, this, ref historizing); + result = onReadHistorizing(context, this, ref historizing); } if (ServiceResult.IsGood(result)) @@ -1523,10 +1541,12 @@ protected override ServiceResult ReadValueAttribute( ServiceResult result = null; + NodeValueEventHandler onReadValue = OnReadValue; + // check if the read behavior has been overridden. - if (OnReadValue != null) + if (onReadValue != null) { - result = OnReadValue( + result = onReadValue( context, this, indexRange, @@ -1549,10 +1569,12 @@ protected override ServiceResult ReadValueAttribute( return result; } + NodeValueSimpleEventHandler onSimpleReadValue = OnSimpleReadValue; + // use default behavior. - if (OnSimpleReadValue != null) + if (onSimpleReadValue != null) { - result = OnSimpleReadValue( + result = onSimpleReadValue( context, this, ref value); @@ -1669,9 +1691,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteDataType != null) + NodeAttributeEventHandler onWriteDataType = OnWriteDataType; + + if (onWriteDataType != null) { - result = OnWriteDataType(context, this, ref dataType); + result = onWriteDataType(context, this, ref dataType); } if (ServiceResult.IsGood(result)) @@ -1698,9 +1722,11 @@ protected override ServiceResult WriteNonValueAttribute( int valueRank = valueRankRef.Value; - if (OnWriteValueRank != null) + NodeAttributeEventHandler onWriteValueRank = OnWriteValueRank; + + if (onWriteValueRank != null) { - result = OnWriteValueRank(context, this, ref valueRank); + result = onWriteValueRank(context, this, ref valueRank); } if (ServiceResult.IsGood(result)) @@ -1720,9 +1746,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteArrayDimensions != null) + NodeAttributeEventHandler> onWriteArrayDimensions = OnWriteArrayDimensions; + + if (onWriteArrayDimensions != null) { - result = OnWriteArrayDimensions(context, this, ref arrayDimensions); + result = onWriteArrayDimensions(context, this, ref arrayDimensions); } if (ServiceResult.IsGood(result)) @@ -1756,9 +1784,11 @@ protected override ServiceResult WriteNonValueAttribute( byte accessLevel = accessLevelRef.Value; - if (OnWriteAccessLevel != null) + NodeAttributeEventHandler onWriteAccessLevel = OnWriteAccessLevel; + + if (onWriteAccessLevel != null) { - result = OnWriteAccessLevel(context, this, ref accessLevel); + result = onWriteAccessLevel(context, this, ref accessLevel); } if (ServiceResult.IsGood(result)) @@ -1785,9 +1815,11 @@ protected override ServiceResult WriteNonValueAttribute( byte userAccessLevel = userAccessLevelRef.Value; - if (OnWriteUserAccessLevel != null) + NodeAttributeEventHandler onWriteUserAccessLevel = OnWriteUserAccessLevel; + + if (onWriteUserAccessLevel != null) { - result = OnWriteUserAccessLevel(context, this, ref userAccessLevel); + result = onWriteUserAccessLevel(context, this, ref userAccessLevel); } if (ServiceResult.IsGood(result)) @@ -1814,9 +1846,11 @@ protected override ServiceResult WriteNonValueAttribute( double minimumSamplingInterval = minimumSamplingIntervalRef.Value; - if (OnWriteMinimumSamplingInterval != null) + NodeAttributeEventHandler onWriteMinimumSamplingInterval = OnWriteMinimumSamplingInterval; + + if (onWriteMinimumSamplingInterval != null) { - result = OnWriteMinimumSamplingInterval(context, this, ref minimumSamplingInterval); + result = onWriteMinimumSamplingInterval(context, this, ref minimumSamplingInterval); } if (ServiceResult.IsGood(result)) @@ -1843,9 +1877,11 @@ protected override ServiceResult WriteNonValueAttribute( bool historizing = historizingRef.Value; - if (OnWriteHistorizing != null) + NodeAttributeEventHandler onWriteHistorizing = OnWriteHistorizing; + + if (onWriteHistorizing != null) { - result = OnWriteHistorizing(context, this, ref historizing); + result = onWriteHistorizing(context, this, ref historizing); } if (ServiceResult.IsGood(result)) @@ -1896,10 +1932,12 @@ protected override ServiceResult WriteValueAttribute( return StatusCodes.BadUserAccessDenied; } + NodeValueEventHandler onWriteValue = OnWriteValue; + // check if the write behavior has been overridden. - if (OnWriteValue != null) + if (onWriteValue != null) { - result = OnWriteValue( + result = onWriteValue( context, this, indexRange, @@ -1969,8 +2007,10 @@ protected override ServiceResult WriteValueAttribute( value = Utils.Clone(value); } + NodeValueSimpleEventHandler onSimpleWriteValue = OnSimpleWriteValue; + // check for simple write value handler. - if (OnSimpleWriteValue != null) + if (onSimpleWriteValue != null) { // index range writes not supported. if (indexRange != NumericRange.Empty) @@ -1978,7 +2018,7 @@ protected override ServiceResult WriteValueAttribute( return StatusCodes.BadIndexRangeInvalid; } - result = OnSimpleWriteValue( + result = onSimpleWriteValue( context, this, ref value); diff --git a/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs index 1ad102dc0b..c23fa1113e 100644 --- a/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/BaseVariableTypeState.cs @@ -441,9 +441,11 @@ protected override ServiceResult ReadNonValueAttribute( { NodeId dataType = m_dataType; - if (OnReadDataType != null) + NodeAttributeEventHandler onReadDataType = OnReadDataType; + + if (onReadDataType != null) { - result = OnReadDataType(context, this, ref dataType); + result = onReadDataType(context, this, ref dataType); } if (ServiceResult.IsGood(result)) @@ -458,9 +460,11 @@ protected override ServiceResult ReadNonValueAttribute( { int valueRank = m_valueRank; - if (OnReadValueRank != null) + NodeAttributeEventHandler onReadValueRank = OnReadValueRank; + + if (onReadValueRank != null) { - result = OnReadValueRank(context, this, ref valueRank); + result = onReadValueRank(context, this, ref valueRank); } if (ServiceResult.IsGood(result)) @@ -475,9 +479,11 @@ protected override ServiceResult ReadNonValueAttribute( { IList arrayDimensions = m_arrayDimensions; - if (OnReadArrayDimensions != null) + NodeAttributeEventHandler> onReadArrayDimensions = OnReadArrayDimensions; + + if (onReadArrayDimensions != null) { - result = OnReadArrayDimensions(context, this, ref arrayDimensions); + result = onReadArrayDimensions(context, this, ref arrayDimensions); } if (ServiceResult.IsGood(result)) @@ -577,9 +583,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteDataType != null) + NodeAttributeEventHandler onWriteDataType = OnWriteDataType; + + if (onWriteDataType != null) { - result = OnWriteDataType(context, this, ref dataType); + result = onWriteDataType(context, this, ref dataType); } if (ServiceResult.IsGood(result)) @@ -606,9 +614,11 @@ protected override ServiceResult WriteNonValueAttribute( int valueRank = valueRankRef.Value; - if (OnWriteValueRank != null) + NodeAttributeEventHandler onWriteValueRank = OnWriteValueRank; + + if (onWriteValueRank != null) { - result = OnWriteValueRank(context, this, ref valueRank); + result = onWriteValueRank(context, this, ref valueRank); } if (ServiceResult.IsGood(result)) @@ -628,9 +638,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteArrayDimensions != null) + NodeAttributeEventHandler> onWriteArrayDimensions = OnWriteArrayDimensions; + + if (onWriteArrayDimensions != null) { - result = OnWriteArrayDimensions(context, this, ref arrayDimensions); + result = onWriteArrayDimensions(context, this, ref arrayDimensions); } if (ServiceResult.IsGood(result)) diff --git a/Stack/Opc.Ua.Core/Stack/State/DataTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/DataTypeState.cs index 4e5e5a4dfe..d00ad302b5 100644 --- a/Stack/Opc.Ua.Core/Stack/State/DataTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/DataTypeState.cs @@ -204,9 +204,11 @@ protected override ServiceResult ReadNonValueAttribute( { ExtensionObject dataTypeDefinition = m_dataTypeDefinition; - if (OnReadDataTypeDefinition != null) + NodeAttributeEventHandler onReadDataTypeDefinition = OnReadDataTypeDefinition; + + if (onReadDataTypeDefinition != null) { - result = OnReadDataTypeDefinition(context, this, ref dataTypeDefinition); + result = onReadDataTypeDefinition(context, this, ref dataTypeDefinition); } if (ServiceResult.IsGood(result)) @@ -256,9 +258,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteDataTypeDefinition != null) + NodeAttributeEventHandler onWriteDataTypeDefinition = OnWriteDataTypeDefinition; + + if (onWriteDataTypeDefinition != null) { - result = OnWriteDataTypeDefinition(context, this, ref dataTypeDefinition); + result = onWriteDataTypeDefinition(context, this, ref dataTypeDefinition); } if (ServiceResult.IsGood(result)) diff --git a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs index 49201d3944..051432f9f3 100644 --- a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs @@ -335,9 +335,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool executable = m_executable; - if (OnReadExecutable != null) + NodeAttributeEventHandler onReadExecutable = OnReadExecutable; + + if (onReadExecutable != null) { - result = OnReadExecutable(context, this, ref executable); + result = onReadExecutable(context, this, ref executable); } if (ServiceResult.IsGood(result)) @@ -352,9 +354,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool userExecutable = m_userExecutable; - if (OnReadUserExecutable != null) + NodeAttributeEventHandler onReadUserExecutable = OnReadUserExecutable; + + if (onReadUserExecutable != null) { - result = OnReadUserExecutable(context, this, ref userExecutable); + result = onReadUserExecutable(context, this, ref userExecutable); } if (ServiceResult.IsGood(result)) @@ -399,9 +403,11 @@ protected override ServiceResult WriteNonValueAttribute( bool executable = executableRef.Value; - if (OnWriteExecutable != null) + NodeAttributeEventHandler onWriteExecutable = OnWriteExecutable; + + if (onWriteExecutable != null) { - result = OnWriteExecutable(context, this, ref executable); + result = onWriteExecutable(context, this, ref executable); } if (ServiceResult.IsGood(result)) @@ -428,9 +434,11 @@ protected override ServiceResult WriteNonValueAttribute( bool userExecutable = userExecutableRef.Value; - if (OnWriteUserExecutable != null) + NodeAttributeEventHandler onWriteUserExecutable = OnWriteUserExecutable; + + if (onWriteUserExecutable != null) { - result = OnWriteUserExecutable(context, this, ref userExecutable); + result = onWriteUserExecutable(context, this, ref userExecutable); } if (ServiceResult.IsGood(result)) @@ -500,14 +508,18 @@ public override void GetChildren( ISystemContext context, IList children) { - if (m_inputArguments != null) + PropertyState inputArguments = m_inputArguments; + + if (inputArguments != null) { - children.Add(m_inputArguments); + children.Add(inputArguments); } - if (m_outputArguments != null) + PropertyState outputArguments = m_outputArguments; + + if (outputArguments != null) { - children.Add(m_outputArguments); + children.Add(outputArguments); } base.GetChildren(context, children); @@ -624,9 +636,11 @@ public virtual ServiceResult Call( // check for too few or too many arguments. int expectedCount = 0; - if (InputArguments != null && InputArguments.Value != null) + PropertyState expectedInputArguments = InputArguments; + + if (expectedInputArguments != null && expectedInputArguments.Value != null) { - expectedCount = InputArguments.Value.Length; + expectedCount = expectedInputArguments.Value.Length; } if (expectedCount > inputArguments.Count) @@ -664,9 +678,11 @@ public virtual ServiceResult Call( // set output arguments to default values. List outputs = new List(); - if (OutputArguments != null) + PropertyState expectedOutputArguments = OutputArguments; + + if (expectedOutputArguments != null) { - IList arguments = OutputArguments.Value; + IList arguments = expectedOutputArguments.Value; if (arguments != null && arguments.Count > 0) { @@ -726,14 +742,18 @@ protected virtual ServiceResult Call( IList inputArguments, IList outputArguments) { - if (OnCallMethod2 != null) + GenericMethodCalledEventHandler2 onCallMethod2 = OnCallMethod2; + + if (onCallMethod2 != null) { - return OnCallMethod2(context, this, objectId, inputArguments, outputArguments); + return onCallMethod2(context, this, objectId, inputArguments, outputArguments); } - if (OnCallMethod != null) + GenericMethodCalledEventHandler onCallMethod = this.OnCallMethod; + + if (onCallMethod != null) { - return OnCallMethod(context, this, inputArguments, outputArguments); + return onCallMethod(context, this, inputArguments, outputArguments); } if (Executable && UserExecutable) @@ -756,12 +776,14 @@ protected ServiceResult ValidateInputArgument( Variant inputArgument, int index) { - if (InputArguments == null) + PropertyState inputArguments = InputArguments; + + if (inputArguments == null) { return StatusCodes.BadInvalidArgument; } - IList arguments = InputArguments.Value; + IList arguments = inputArguments.Value; if (arguments == null || index < 0 || index >= arguments.Count) { diff --git a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs index 96753d26fa..2b0f9b6f1c 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs @@ -67,13 +67,21 @@ public virtual object Clone() /// protected object CloneChildren(NodeState clone) { - if (m_children != null) + + List children; + + lock (m_childrenLock) + { + children = m_children != null ? new List(m_children) : null; + } + + if (children != null) { - clone.m_children = new List(m_children.Count); + clone.m_children = new List(children.Count); - for (int ii = 0; ii < m_children.Count; ii++) + for (int ii = 0; ii < children.Count; ii++) { - BaseInstanceState child = (BaseInstanceState)m_children[ii].Clone(); + BaseInstanceState child = (BaseInstanceState)children[ii].Clone(); clone.m_children.Add(child); } } @@ -1190,18 +1198,30 @@ public void UpdateReferences(ISystemContext context, BinaryDecoder decoder) { int count = decoder.ReadInt32(null); + // Collect references to temporary list first to avoid unnecessary locking during the deserialization. + List references = new List(); + for (int ii = 0; ii < count; ii++) { NodeId referenceTypeId = decoder.ReadNodeId(null); bool isInverse = decoder.ReadBoolean(null); ExpandedNodeId targetId = decoder.ReadExpandedNodeId(null); + references.Add(new NodeStateReference(referenceTypeId, isInverse, targetId)); + + } + + lock (m_referencesLock) + { if (m_references == null) { m_references = new IReferenceDictionary(); } - m_references[new NodeStateReference(referenceTypeId, isInverse, targetId)] = null; + foreach (NodeStateReference reference in references) + { + m_references[reference] = null; + } } } @@ -2307,13 +2327,16 @@ public bool ValidationRequired /// Whether to recursively set the flag on any children. public void SetAreEventsMonitored(ISystemContext context, bool areEventsMonitored, bool includeChildren) { - if (areEventsMonitored) + lock (m_areEventsMonitoredLock) { - m_areEventsMonitored++; - } - else if (m_areEventsMonitored > 0) - { - m_areEventsMonitored--; + if (areEventsMonitored) + { + m_areEventsMonitored++; + } + else if (m_areEventsMonitored > 0) + { + m_areEventsMonitored--; + } } // propagate monitoring flag to children. @@ -2327,14 +2350,21 @@ public void SetAreEventsMonitored(ISystemContext context, bool areEventsMonitore children[ii].SetAreEventsMonitored(context, areEventsMonitored, true); } + List notifiers; + + lock (m_notifiersLock) + { + notifiers = m_notifiers != null ? new List(m_notifiers) : null; + } + // propagate monitoring flag to target notifiers. - if (m_notifiers != null) + if (notifiers != null) { - for (int ii = 0; ii < m_notifiers.Count; ii++) + for (int ii = 0; ii < notifiers.Count; ii++) { - if (!m_notifiers[ii].IsInverse) + if (!notifiers[ii].IsInverse) { - m_notifiers[ii].Node.SetAreEventsMonitored(context, areEventsMonitored, includeChildren); + notifiers[ii].Node.SetAreEventsMonitored(context, areEventsMonitored, includeChildren); } } } @@ -2350,14 +2380,21 @@ public virtual void ReportEvent(ISystemContext context, IFilterTarget e) { OnReportEvent?.Invoke(context, this, e); + List notifiers; + + lock (m_notifiersLock) + { + notifiers = m_notifiers != null ? new List(m_notifiers) : null; + } + // report event to notifier sources. - if (m_notifiers != null) + if (notifiers != null) { - for (int ii = 0; ii < m_notifiers.Count; ii++) + for (int ii = 0; ii < notifiers.Count; ii++) { - if (m_notifiers[ii].IsInverse) + if (notifiers[ii].IsInverse) { - m_notifiers[ii].Node.ReportEvent(context, e); + notifiers[ii].Node.ReportEvent(context, e); } } } @@ -2376,44 +2413,48 @@ public virtual void AddNotifier( bool isInverse, NodeState target) { - if (m_notifiers == null) - { - m_notifiers = new List(); - } if (NodeId.IsNull(referenceTypeId)) { referenceTypeId = ReferenceTypeIds.HasEventSource; } - // check for existing reference. - Notifier entry = null; - - for (int ii = 0; ii < m_notifiers.Count; ii++) - { - if (Object.ReferenceEquals(m_notifiers[ii].Node, target)) - { - entry = m_notifiers[ii]; - break; - } - } - // ensure duplicate references are not left over from the model design. if (!NodeId.IsNull(target.NodeId)) { RemoveReference(referenceTypeId, isInverse, target.NodeId); } - if (entry == null) + lock (m_notifiersLock) { - entry = new Notifier(); - m_notifiers.Add(entry); - } + if (m_notifiers == null) + { + m_notifiers = new List(); + } + + // check for existing reference. + Notifier entry = null; + + for (int ii = 0; ii < m_notifiers.Count; ii++) + { + if (Object.ReferenceEquals(m_notifiers[ii].Node, target)) + { + entry = m_notifiers[ii]; + break; + } + } + + if (entry == null) + { + entry = new Notifier(); + m_notifiers.Add(entry); + } - // save the notifier. - entry.ReferenceTypeId = referenceTypeId; - entry.IsInverse = isInverse; - entry.Node = target; + // save the notifier. + entry.ReferenceTypeId = referenceTypeId; + entry.IsInverse = isInverse; + entry.Node = target; + } } /// @@ -2424,28 +2465,35 @@ public virtual void AddNotifier( /// Whether the inverse relationship should be removed from the target. public virtual void RemoveNotifier(ISystemContext context, NodeState target, bool bidirectional) { - if (m_notifiers != null) + NodeState nodeState = null; + + lock (m_notifiersLock) { - for (int ii = 0; ii < m_notifiers.Count; ii++) - { - Notifier entry = m_notifiers[ii]; - if (Object.ReferenceEquals(entry.Node, target)) + if (m_notifiers != null) + { + for (int ii = 0; ii < m_notifiers.Count; ii++) { - if (bidirectional) + Notifier entry = m_notifiers[ii]; + + if (Object.ReferenceEquals(entry.Node, target)) { - entry.Node.RemoveNotifier(context, this, false); + nodeState = entry.Node; + m_notifiers.RemoveAt(ii); + break; } + } - m_notifiers.RemoveAt(ii); - break; + if (m_notifiers.Count == 0) + { + m_notifiers = null; } } + } - if (m_notifiers.Count == 0) - { - m_notifiers = null; - } + if (nodeState != null && bidirectional) + { + nodeState.RemoveNotifier(context, this, false); } } @@ -2458,11 +2506,14 @@ public virtual void GetNotifiers( ISystemContext context, IList notifiers) { - if (m_notifiers != null) + lock (m_notifiersLock) { - foreach (Notifier notifier in m_notifiers) + if (m_notifiers != null) { - notifiers.Add(notifier); + foreach (Notifier notifier in m_notifiers) + { + notifiers.Add(notifier); + } } } } @@ -2476,13 +2527,16 @@ public virtual void GetNotifiers( NodeId notifierTypeId, bool isInverse) { - if (m_notifiers != null) + lock (m_notifiersLock) { - foreach (Notifier notifier in m_notifiers) + if (m_notifiers != null) { - if (isInverse == notifier.IsInverse && notifier.ReferenceTypeId == notifierTypeId) + foreach (Notifier notifier in m_notifiers) { - notifiers.Add(notifier); + if (isInverse == notifier.IsInverse && notifier.ReferenceTypeId == notifierTypeId) + { + notifiers.Add(notifier); + } } } } @@ -2509,14 +2563,21 @@ public virtual void ConditionRefresh(ISystemContext context, List children[ii].ConditionRefresh(context, events, true); } + List notifiers; + + lock (m_notifiersLock) + { + notifiers = m_notifiers != null ? new List(m_notifiers) : null; + } + // request events from notifier targets. - if (m_notifiers != null) + if (notifiers != null) { - for (int ii = 0; ii < m_notifiers.Count; ii++) + for (int ii = 0; ii < notifiers.Count; ii++) { - if (!m_notifiers[ii].IsInverse) + if (!notifiers[ii].IsInverse) { - m_notifiers[ii].Node.ConditionRefresh(context, events, true); + notifiers[ii].Node.ConditionRefresh(context, events, true); } } } @@ -2574,12 +2635,12 @@ public void ClearChangeMasks(ISystemContext context, bool includeChildren) } } - if (m_changeMasks != NodeStateChangeMasks.None) - { - OnStateChanged?.Invoke(context, this, m_changeMasks); - - StateChanged?.Invoke(context, this, m_changeMasks); + NodeStateChangeMasks changeMasks = m_changeMasks; + if (changeMasks != NodeStateChangeMasks.None) + { + OnStateChanged?.Invoke(context, this, changeMasks); + StateChanged?.Invoke(context, this, changeMasks); m_changeMasks = NodeStateChangeMasks.None; } } @@ -2838,9 +2899,11 @@ private void AssignNodeIds( /// True if the node is currently valid. public virtual bool Validate(ISystemContext context) { - if (OnValidate != null) + NodeStateValidateHandler onValidate = OnValidate; + + if (onValidate != null) { - return OnValidate(context, this); + return onValidate(context, this); } return true; @@ -2868,22 +2931,17 @@ public virtual INodeBrowser CreateBrowser( IEnumerable additionalReferences, bool internalOnly) { - NodeBrowser browser = null; - // see if a callback has been provided. - if (OnCreateBrowser != null) - { - browser = OnCreateBrowser( - context, - this, - view, - referenceType, - includeSubtypes, - browseDirection, - browseName, - additionalReferences, - internalOnly); - } + NodeBrowser browser = OnCreateBrowser?.Invoke( + context, + this, + view, + referenceType, + includeSubtypes, + browseDirection, + browseName, + additionalReferences, + internalOnly); // use default browser. if (browser == null) @@ -2951,28 +3009,31 @@ public void GetHierarchyReferences( Dictionary hierarchy, List references) { - // index any references. - if (m_references != null) + lock (m_referencesLock) { - foreach (IReference reference in m_references.Keys) + // index any references. + if (m_references != null) { - NodeId targetId = ExpandedNodeId.ToNodeId(reference.TargetId, context.NamespaceUris); - - if (targetId == null) + foreach (IReference reference in m_references.Keys) { - references.Add(new NodeStateHierarchyReference(browsePath, reference)); - continue; - } + NodeId targetId = ExpandedNodeId.ToNodeId(reference.TargetId, context.NamespaceUris); - string targetPath = null; + if (targetId == null) + { + references.Add(new NodeStateHierarchyReference(browsePath, reference)); + continue; + } - if (!hierarchy.TryGetValue(targetId, out targetPath)) - { - references.Add(new NodeStateHierarchyReference(browsePath, reference)); - continue; - } + string targetPath = null; - references.Add(new NodeStateHierarchyReference(browsePath, targetPath, reference)); + if (!hierarchy.TryGetValue(targetId, out targetPath)) + { + references.Add(new NodeStateHierarchyReference(browsePath, reference)); + continue; + } + + references.Add(new NodeStateHierarchyReference(browsePath, targetPath, reference)); + } } } @@ -3007,47 +3068,50 @@ private void UpdateReferenceTargets( List children, Dictionary mappingTable) { - // check if there are references to update. - if (m_references != null) + lock (m_referencesLock) { - List referencesToAdd = new List(); - List referencesToRemove = new List(); - - foreach (IReference reference in m_references.Keys) + // check if there are references to update. + if (m_references != null) { - // check for absolute id. - NodeId oldId = ExpandedNodeId.ToNodeId(reference.TargetId, context.NamespaceUris); + List referencesToAdd = new List(); + List referencesToRemove = new List(); - if (oldId == null) + foreach (IReference reference in m_references.Keys) { - continue; - } + // check for absolute id. + NodeId oldId = ExpandedNodeId.ToNodeId(reference.TargetId, context.NamespaceUris); - // look up new node id. - NodeId newId = null; + if (oldId == null) + { + continue; + } + + // look up new node id. + NodeId newId = null; - if (mappingTable.TryGetValue(oldId, out newId)) + if (mappingTable.TryGetValue(oldId, out newId)) + { + referencesToRemove.Add(reference); + referencesToAdd.Add(new NodeStateReference(reference.ReferenceTypeId, reference.IsInverse, newId)); + } + } + + // remove old references. + for (int ii = 0; ii < referencesToRemove.Count; ii++) { - referencesToRemove.Add(reference); - referencesToAdd.Add(new NodeStateReference(reference.ReferenceTypeId, reference.IsInverse, newId)); + if (m_references.Remove(referencesToRemove[ii])) + { + m_changeMasks |= NodeStateChangeMasks.References; + } } - } - // remove old references. - for (int ii = 0; ii < referencesToRemove.Count; ii++) - { - if (m_references.Remove(referencesToRemove[ii])) + // add new references. + for (int ii = 0; ii < referencesToAdd.Count; ii++) { + m_references[referencesToAdd[ii]] = null; m_changeMasks |= NodeStateChangeMasks.References; } } - - // add new references. - for (int ii = 0; ii < referencesToAdd.Count; ii++) - { - m_references[referencesToAdd[ii]] = null; - m_changeMasks |= NodeStateChangeMasks.References; - } } // recursively update targets for children. @@ -3107,84 +3171,105 @@ protected virtual void PopulateBrowser(ISystemContext context, NodeBrowser brows } } + List notifiers; + + lock (m_notifiersLock) + { + notifiers = m_notifiers != null ? new List(m_notifiers) : null; + } + // add any notifiers. - if (m_notifiers != null) + if (notifiers != null) { - for (int ii = 0; ii < m_notifiers.Count; ii++) + for (int ii = 0; ii < notifiers.Count; ii++) { - Notifier entry = m_notifiers[ii]; + Notifier entry = notifiers[ii]; if (browser.IsRequired(entry.ReferenceTypeId, entry.IsInverse)) { - browser.Add(entry.ReferenceTypeId, entry.IsInverse, m_notifiers[ii].Node); + browser.Add(entry.ReferenceTypeId, entry.IsInverse, notifiers[ii].Node); } } } - // add any arbitrary references. - if (m_references != null) + List referencesToAdd = new List(); + + BrowseDirection browserBrowseDirection = browser.BrowseDirection; + bool browserIncludeSubtypes = browser.IncludeSubtypes; + NodeId browserReferenceType = browser.ReferenceType; + + lock (m_referencesLock) { - if (referenceTypeId == null) + // add any arbitrary references. + if (m_references != null) { - foreach (IReference reference in m_references.Keys) + if (referenceTypeId == null) { - if (reference.IsInverse) + foreach (IReference reference in m_references.Keys) { - if (browser.BrowseDirection == BrowseDirection.Forward) + if (reference.IsInverse) { - continue; + if (browserBrowseDirection == BrowseDirection.Forward) + { + continue; + } } - } - else - { - if (browser.BrowseDirection == BrowseDirection.Inverse) + else { - continue; + if (browserBrowseDirection == BrowseDirection.Inverse) + { + continue; + } } - } - browser.Add(reference); + referencesToAdd.Add(reference); + } } - } - else - { - IList references = null; - - if (browser.BrowseDirection != BrowseDirection.Inverse) + else { - if (browser.IncludeSubtypes) - { - references = m_references.Find(browser.ReferenceType, false, context.TypeTable); - } - else - { - references = m_references.Find(browser.ReferenceType, false); - } + IList references = null; - for (int ii = 0; ii < references.Count; ii++) + if (browserBrowseDirection != BrowseDirection.Inverse) { - browser.Add(references[ii]); - } - } + if (browserIncludeSubtypes) + { + references = m_references.Find(browserReferenceType, false, context.TypeTable); + } + else + { + references = m_references.Find(browserReferenceType, false); + } - if (browser.BrowseDirection != BrowseDirection.Forward) - { - if (browser.IncludeSubtypes) - { - references = m_references.Find(browser.ReferenceType, true, context.TypeTable); - } - else - { - references = m_references.Find(browser.ReferenceType, true); + for (int ii = 0; ii < references.Count; ii++) + { + referencesToAdd.Add(references[ii]); + } } - for (int ii = 0; ii < references.Count; ii++) + if (browserBrowseDirection != BrowseDirection.Forward) { - browser.Add(references[ii]); + if (browserIncludeSubtypes) + { + references = m_references.Find(browserReferenceType, true, context.TypeTable); + } + else + { + references = m_references.Find(browserReferenceType, true); + } + + for (int ii = 0; ii < references.Count; ii++) + { + referencesToAdd.Add(references[ii]); + } } } } } + + foreach (var reference in referencesToAdd) + { + browser.Add(reference); + } } /// @@ -3382,9 +3467,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { NodeId nodeId = m_nodeId; - if (OnReadNodeId != null) + NodeAttributeEventHandler onReadNodeId = OnReadNodeId; + + if (onReadNodeId != null) { - result = OnReadNodeId(context, this, ref nodeId); + result = onReadNodeId(context, this, ref nodeId); } if (ServiceResult.IsGood(result)) @@ -3399,9 +3486,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { NodeClass nodeClass = m_nodeClass; - if (OnReadNodeClass != null) + NodeAttributeEventHandler onReadNodeClass = OnReadNodeClass; + + if (onReadNodeClass != null) { - result = OnReadNodeClass(context, this, ref nodeClass); + result = onReadNodeClass(context, this, ref nodeClass); } if (ServiceResult.IsGood(result)) @@ -3416,9 +3505,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { QualifiedName browseName = m_browseName; - if (OnReadBrowseName != null) + NodeAttributeEventHandler onReadBrowseName = OnReadBrowseName; + + if (onReadBrowseName != null) { - result = OnReadBrowseName(context, this, ref browseName); + result = onReadBrowseName(context, this, ref browseName); } if (ServiceResult.IsGood(result)) @@ -3433,9 +3524,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { LocalizedText displayName = m_displayName; - if (OnReadDisplayName != null) + NodeAttributeEventHandler onReadDisplayName = OnReadDisplayName; + + if (onReadDisplayName != null) { - result = OnReadDisplayName(context, this, ref displayName); + result = onReadDisplayName(context, this, ref displayName); } if (ServiceResult.IsGood(result)) @@ -3455,9 +3548,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { LocalizedText description = m_description; - if (OnReadDescription != null) + NodeAttributeEventHandler onReadDescription = OnReadDescription; + + if (onReadDescription != null) { - result = OnReadDescription(context, this, ref description); + result = onReadDescription(context, this, ref description); } if (ServiceResult.IsGood(result)) @@ -3477,9 +3572,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { AttributeWriteMask writeMask = m_writeMask; - if (OnReadWriteMask != null) + NodeAttributeEventHandler onReadWriteMask = OnReadWriteMask; + + if (onReadWriteMask != null) { - result = OnReadWriteMask(context, this, ref writeMask); + result = onReadWriteMask(context, this, ref writeMask); } if (ServiceResult.IsGood(result)) @@ -3494,9 +3591,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { AttributeWriteMask userWriteMask = m_userWriteMask; - if (OnReadUserWriteMask != null) + NodeAttributeEventHandler onReadUserWriteMask = OnReadUserWriteMask; + + if (onReadUserWriteMask != null) { - result = OnReadUserWriteMask(context, this, ref userWriteMask); + result = onReadUserWriteMask(context, this, ref userWriteMask); } if (ServiceResult.IsGood(result)) @@ -3511,9 +3610,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { RolePermissionTypeCollection rolePermissions = m_rolePermissions; - if (OnReadRolePermissions != null) + NodeAttributeEventHandler onReadRolePermissions = OnReadRolePermissions; + + if (onReadRolePermissions != null) { - result = OnReadRolePermissions(context, this, ref rolePermissions); + result = onReadRolePermissions(context, this, ref rolePermissions); } if (ServiceResult.IsGood(result)) @@ -3533,9 +3634,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { RolePermissionTypeCollection userRolePermissions = m_userRolePermissions; - if (OnReadUserRolePermissions != null) + NodeAttributeEventHandler onReadUserRolePermissions = OnReadUserRolePermissions; + + if (onReadUserRolePermissions != null) { - result = OnReadUserRolePermissions(context, this, ref userRolePermissions); + result = onReadUserRolePermissions(context, this, ref userRolePermissions); } if (ServiceResult.IsGood(result)) @@ -3555,9 +3658,11 @@ protected virtual ServiceResult ReadNonValueAttribute( { AccessRestrictionType? accessRestrictions = m_accessRestrictions; - if (OnReadAccessRestrictions != null) + NodeAttributeEventHandler onReadAccessRestrictions = OnReadAccessRestrictions; + + if (onReadAccessRestrictions != null) { - result = OnReadAccessRestrictions(context, this, ref accessRestrictions); + result = onReadAccessRestrictions(context, this, ref accessRestrictions); } if (ServiceResult.IsGood(result)) @@ -3708,9 +3813,11 @@ protected virtual ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteNodeId != null) + NodeAttributeEventHandler onWriteNodeId = OnWriteNodeId; + + if (onWriteNodeId != null) { - result = OnWriteNodeId(context, this, ref nodeId); + result = onWriteNodeId(context, this, ref nodeId); } if (ServiceResult.IsGood(result)) @@ -3737,9 +3844,11 @@ protected virtual ServiceResult WriteNonValueAttribute( NodeClass nodeClass = (NodeClass)nodeClassRef.Value; - if (OnWriteNodeClass != null) + NodeAttributeEventHandler onWriteNodeClass = OnWriteNodeClass; + + if (onWriteNodeClass != null) { - result = OnWriteNodeClass(context, this, ref nodeClass); + result = onWriteNodeClass(context, this, ref nodeClass); } if (ServiceResult.IsGood(result)) @@ -3764,9 +3873,11 @@ protected virtual ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteBrowseName != null) + NodeAttributeEventHandler onWriteBrowseName = OnWriteBrowseName; + + if (onWriteBrowseName != null) { - result = OnWriteBrowseName(context, this, ref browseName); + result = onWriteBrowseName(context, this, ref browseName); } if (ServiceResult.IsGood(result)) @@ -3791,9 +3902,11 @@ protected virtual ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteDisplayName != null) + NodeAttributeEventHandler onWriteDisplayName = OnWriteDisplayName; + + if (onWriteDisplayName != null) { - result = OnWriteDisplayName(context, this, ref displayName); + result = onWriteDisplayName(context, this, ref displayName); } if (ServiceResult.IsGood(result)) @@ -3818,9 +3931,11 @@ protected virtual ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteDescription != null) + NodeAttributeEventHandler onWriteDescription = OnWriteDescription; + + if (onWriteDescription != null) { - result = OnWriteDescription(context, this, ref description); + result = onWriteDescription(context, this, ref description); } if (ServiceResult.IsGood(result)) @@ -3847,9 +3962,11 @@ protected virtual ServiceResult WriteNonValueAttribute( AttributeWriteMask writeMask = (AttributeWriteMask)writeMaskRef.Value; - if (OnWriteWriteMask != null) + NodeAttributeEventHandler onWriteWriteMask = this.OnWriteWriteMask; + + if (onWriteWriteMask != null) { - result = OnWriteWriteMask(context, this, ref writeMask); + result = onWriteWriteMask(context, this, ref writeMask); } if (ServiceResult.IsGood(result)) @@ -3876,9 +3993,11 @@ protected virtual ServiceResult WriteNonValueAttribute( AttributeWriteMask userWriteMask = (AttributeWriteMask)userWriteMaskRef.Value; - if (OnWriteUserWriteMask != null) + NodeAttributeEventHandler onWriteUserWriteMask = this.OnWriteUserWriteMask; + + if (onWriteUserWriteMask != null) { - result = OnWriteUserWriteMask(context, this, ref userWriteMask); + result = onWriteUserWriteMask(context, this, ref userWriteMask); } if (ServiceResult.IsGood(result)) @@ -3915,9 +4034,11 @@ protected virtual ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteRolePermissions != null) + NodeAttributeEventHandler onWriteRolePermissions = OnWriteRolePermissions; + + if (onWriteRolePermissions != null) { - result = OnWriteRolePermissions(context, this, ref rolePermissions); + result = onWriteRolePermissions(context, this, ref rolePermissions); } if (ServiceResult.IsGood(result)) @@ -3951,9 +4072,11 @@ protected virtual ServiceResult WriteNonValueAttribute( var accessRestrictions = (AccessRestrictionType?)accessRestrictionsRef.Value; - if (OnWriteAccessRestrictions != null) + NodeAttributeEventHandler onWriteAccessRestrictions = OnWriteAccessRestrictions; + + if (onWriteAccessRestrictions != null) { - result = OnWriteAccessRestrictions(context, this, ref accessRestrictions); + result = onWriteAccessRestrictions(context, this, ref accessRestrictions); } if (ServiceResult.IsGood(result)) @@ -4165,12 +4288,17 @@ public void AddChild(BaseInstanceState child) } } - if (m_children == null) + lock (m_childrenLock) { - m_children = new List(); + + if (m_children == null) + { + m_children = new List(); + } + + m_children.Add(child); } - m_children.Add(child); m_changeMasks |= NodeStateChangeMasks.Children; } @@ -4210,16 +4338,19 @@ public PropertyState AddProperty(string propertyName, NodeId dataTypeId, int /// public void RemoveChild(BaseInstanceState child) { - if (m_children != null) + lock (m_childrenLock) { - for (int ii = 0; ii < m_children.Count; ii++) + if (m_children != null) { - if (Object.ReferenceEquals(m_children[ii], child)) + for (int ii = 0; ii < m_children.Count; ii++) { - child.Parent = null; - m_children.RemoveAt(ii); - m_changeMasks |= NodeStateChangeMasks.Children; - return; + if (Object.ReferenceEquals(m_children[ii], child)) + { + child.Parent = null; + m_children.RemoveAt(ii); + m_changeMasks |= NodeStateChangeMasks.Children; + return; + } } } } @@ -4372,17 +4503,27 @@ public ServiceResult WriteChildAttribute( return WriteAttribute(context, attributeId, NumericRange.Empty, value); } + List children = null; + + lock (m_childrenLock) + { + if (m_children != null) + { + children = new List(m_children); + } + } + // recursively update children. - if (m_children != null) + if (children != null) { - for (int ii = 0; ii < m_children.Count; ii++) + for (int ii = 0; ii < children.Count; ii++) { - if (componentPath[index] != m_children[ii].BrowseName) + if (componentPath[index] != children[ii].BrowseName) { continue; } - return m_children[ii].WriteChildAttribute( + return children[ii].WriteChildAttribute( context, componentPath, index + 1, @@ -4406,12 +4547,15 @@ public bool ReferenceExists( bool isInverse, ExpandedNodeId targetId) { - if (m_references == null || referenceTypeId == null || targetId == null) + lock (m_referencesLock) { - return false; - } + if (m_references == null || referenceTypeId == null || targetId == null) + { + return false; + } - return m_references.ContainsKey(new NodeStateReference(referenceTypeId, isInverse, targetId)); + return m_references.ContainsKey(new NodeStateReference(referenceTypeId, isInverse, targetId)); + } } /// @@ -4428,12 +4572,16 @@ public void AddReference( if (NodeId.IsNull(referenceTypeId)) throw new ArgumentNullException(nameof(referenceTypeId)); if (NodeId.IsNull(targetId)) throw new ArgumentNullException(nameof(targetId)); - if (m_references == null) + lock (m_referencesLock) { - m_references = new IReferenceDictionary(); + if (m_references == null) + { + m_references = new IReferenceDictionary(); + } + + m_references.Add(new NodeStateReference(referenceTypeId, isInverse, targetId), null); } - m_references.Add(new NodeStateReference(referenceTypeId, isInverse, targetId), null); m_changeMasks |= NodeStateChangeMasks.References; OnReferenceAdded?.Invoke(this, referenceTypeId, isInverse, targetId); @@ -4453,19 +4601,29 @@ public bool RemoveReference( if (NodeId.IsNull(referenceTypeId)) throw new ArgumentNullException(nameof(referenceTypeId)); if (NodeId.IsNull(targetId)) throw new ArgumentNullException(nameof(targetId)); - if (m_references == null) + bool removed = false; + + lock (m_referencesLock) { - return false; + if (m_references == null) + { + return false; + } + + if (m_references.Remove(new NodeStateReference(referenceTypeId, isInverse, targetId))) + { + m_changeMasks |= NodeStateChangeMasks.References; + OnReferenceRemoved?.Invoke(this, referenceTypeId, isInverse, targetId); + return true; + } } - if (m_references.Remove(new NodeStateReference(referenceTypeId, isInverse, targetId))) + if (removed) { - m_changeMasks |= NodeStateChangeMasks.References; OnReferenceRemoved?.Invoke(this, referenceTypeId, isInverse, targetId); - return true; } - return false; + return removed; } /// @@ -4476,20 +4634,31 @@ public void AddReferences(IList references) { if (references == null) throw new ArgumentNullException(nameof(references)); - if (m_references == null) - { - m_references = new IReferenceDictionary(); - } + List addedReferences = new List(); - for (int ii = 0; ii < references.Count; ii++) + lock (m_referencesLock) { - if (!m_references.ContainsKey(references[ii])) + if (m_references == null) + { + m_references = new IReferenceDictionary(); + } + + for (int ii = 0; ii < references.Count; ii++) { - m_references.Add(references[ii], null); - OnReferenceAdded?.Invoke(this, references[ii].ReferenceTypeId, references[ii].IsInverse, references[ii].TargetId); + if (!m_references.ContainsKey(references[ii])) + { + m_references.Add(references[ii], null); + addedReferences.Add(references[ii]); + + } } } + foreach (IReference addedReference in addedReferences) + { + OnReferenceAdded?.Invoke(this, addedReference.ReferenceTypeId, addedReference.IsInverse, addedReference.TargetId); + } + m_changeMasks |= NodeStateChangeMasks.References; } @@ -4504,15 +4673,20 @@ public bool RemoveReferences( { if (NodeId.IsNull(referenceTypeId)) throw new ArgumentNullException(nameof(referenceTypeId)); - if (m_references == null) + List refsToRemove = null; + + lock (m_referencesLock) { - return false; - } + if (m_references == null) + { + return false; + } - var refsToRemove = m_references - .Select(r => r.Key) - .Where(r => r.ReferenceTypeId == referenceTypeId && r.IsInverse == isInverse) - .ToList(); + refsToRemove = m_references + .Select(r => r.Key) + .Where(r => r.ReferenceTypeId == referenceTypeId && r.IsInverse == isInverse) + .ToList(); + } refsToRemove.ForEach(r => RemoveReference(r.ReferenceTypeId, r.IsInverse, r.TargetId)); @@ -4534,11 +4708,14 @@ public virtual void GetChildren( ISystemContext context, IList children) { - if (m_children != null) + lock (m_childrenLock) { - for (int ii = 0; ii < m_children.Count; ii++) + if (m_children != null) { - children.Add(m_children[ii]); + for (int ii = 0; ii < m_children.Count; ii++) + { + children.Add(m_children[ii]); + } } } } @@ -4559,11 +4736,14 @@ public virtual void GetReferences( ISystemContext context, IList references) { - if (m_references != null) + lock (m_referencesLock) { - foreach (IReference reference in m_references.Keys) + if (m_references != null) { - references.Add(reference); + foreach (IReference reference in m_references.Keys) + { + references.Add(reference); + } } } } @@ -4577,13 +4757,16 @@ public virtual void GetReferences( NodeId referenceTypeId, bool isInverse) { - if (m_references != null) + lock (m_referencesLock) { - foreach (IReference reference in m_references.Keys) + if (m_references != null) { - if (isInverse == reference.IsInverse && reference.ReferenceTypeId == referenceTypeId) + foreach (IReference reference in m_references.Keys) { - references.Add(reference); + if (isInverse == reference.IsInverse && reference.ReferenceTypeId == referenceTypeId) + { + references.Add(reference); + } } } } @@ -4608,20 +4791,23 @@ protected virtual BaseInstanceState FindChild( return null; } - if (m_children != null) + lock (m_childrenLock) { - for (int ii = 0; ii < m_children.Count; ii++) + if (m_children != null) { - BaseInstanceState child = m_children[ii]; - - if (browseName == child.BrowseName) + for (int ii = 0; ii < m_children.Count; ii++) { - if (createOrReplace && replacement != null) + BaseInstanceState child = m_children[ii]; + + if (browseName == child.BrowseName) { - m_children[ii] = child = replacement; - } + if (createOrReplace && replacement != null) + { + m_children[ii] = child = replacement; + } - return child; + return child; + } } } } @@ -4674,6 +4860,10 @@ public class Notifier #endregion #region Private Fields + private readonly object m_areEventsMonitoredLock = new object(); + private readonly object m_notifiersLock = new object(); + private readonly object m_referencesLock = new object(); + private readonly object m_childrenLock = new object(); private object m_handle; private string m_symbolicName; private NodeId m_nodeId; diff --git a/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs b/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs index 5b8426c4f4..cf400c359e 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs @@ -302,9 +302,11 @@ protected override ServiceResult ReadNonValueAttribute( { LocalizedText inverseName = m_inverseName; - if (OnReadInverseName != null) + NodeAttributeEventHandler onReadInverseName = OnReadInverseName; + + if (onReadInverseName != null) { - result = OnReadInverseName(context, this, ref inverseName); + result = onReadInverseName(context, this, ref inverseName); } if (ServiceResult.IsGood(result)) @@ -326,9 +328,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool symmetric = m_symmetric; - if (OnReadSymmetric != null) + NodeAttributeEventHandler onReadSymmetric = OnReadSymmetric; + + if (onReadSymmetric != null) { - result = OnReadSymmetric(context, this, ref symmetric); + result = onReadSymmetric(context, this, ref symmetric); } if (ServiceResult.IsGood(result)) @@ -371,9 +375,11 @@ protected override ServiceResult WriteNonValueAttribute( return StatusCodes.BadNotWritable; } - if (OnWriteInverseName != null) + NodeAttributeEventHandler onWriteInverseName = OnWriteInverseName; + + if (onWriteInverseName != null) { - result = OnWriteInverseName(context, this, ref inverseName); + result = onWriteInverseName(context, this, ref inverseName); } if (ServiceResult.IsGood(result)) @@ -400,9 +406,11 @@ protected override ServiceResult WriteNonValueAttribute( bool symmetric = symmetricRef.Value; - if (OnWriteSymmetric != null) + NodeAttributeEventHandler onWriteSymmetric = OnWriteSymmetric; + + if (onWriteSymmetric != null) { - result = OnWriteSymmetric(context, this, ref symmetric); + result = onWriteSymmetric(context, this, ref symmetric); } if (ServiceResult.IsGood(result)) diff --git a/Stack/Opc.Ua.Core/Stack/State/ViewState.cs b/Stack/Opc.Ua.Core/Stack/State/ViewState.cs index 2f37f33e9f..1b7e4da4f3 100644 --- a/Stack/Opc.Ua.Core/Stack/State/ViewState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/ViewState.cs @@ -305,9 +305,11 @@ protected override ServiceResult ReadNonValueAttribute( { byte eventNotifier = m_eventNotifier; - if (OnReadEventNotifier != null) + NodeAttributeEventHandler onReadEventNotifier = OnReadEventNotifier; + + if (onReadEventNotifier != null) { - result = OnReadEventNotifier(context, this, ref eventNotifier); + result = onReadEventNotifier(context, this, ref eventNotifier); } if (ServiceResult.IsGood(result)) @@ -322,9 +324,11 @@ protected override ServiceResult ReadNonValueAttribute( { bool containsNoLoops = m_containsNoLoops; - if (OnReadContainsNoLoops != null) + NodeAttributeEventHandler onReadContainsNoLoops = OnReadContainsNoLoops; + + if (onReadContainsNoLoops != null) { - result = OnReadContainsNoLoops(context, this, ref containsNoLoops); + result = onReadContainsNoLoops(context, this, ref containsNoLoops); } if (ServiceResult.IsGood(result)) @@ -369,9 +373,11 @@ protected override ServiceResult WriteNonValueAttribute( byte eventNotifier = eventNotifierRef.Value; - if (OnWriteEventNotifier != null) + NodeAttributeEventHandler onWriteEventNotifier = OnWriteEventNotifier; + + if (onWriteEventNotifier != null) { - result = OnWriteEventNotifier(context, this, ref eventNotifier); + result = onWriteEventNotifier(context, this, ref eventNotifier); } if (ServiceResult.IsGood(result)) @@ -398,9 +404,11 @@ protected override ServiceResult WriteNonValueAttribute( bool containsNoLoops = containsNoLoopsRef.Value; - if (OnWriteContainsNoLoops != null) + NodeAttributeEventHandler onWriteContainsNoLoops = OnWriteContainsNoLoops; + + if (onWriteContainsNoLoops != null) { - result = OnWriteContainsNoLoops(context, this, ref containsNoLoops); + result = onWriteContainsNoLoops(context, this, ref containsNoLoops); } if (ServiceResult.IsGood(result)) diff --git a/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateCollectionConcurrencyTests.cs b/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateCollectionConcurrencyTests.cs new file mode 100644 index 0000000000..0867ef84d3 --- /dev/null +++ b/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateCollectionConcurrencyTests.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.Core.Tests.Stack.State +{ + /// + /// Tests for concurrency issues in BaseVariableState class + /// + [TestFixture] + [SetCulture("en-us"), SetUICulture("en-us")] + [Category("NodeStateConcurrency")] + [Parallelizable(ParallelScope.All)] + public class NodeStateCollectionConcurrencyTests + { + [Test] + public void NodeStateReferencesCollectionConcurrencyTest() + { + var testNodeState = new AnalogUnitRangeState(null); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + List references = new List(); + + testNodeState.GetReferences(systemContext, references); + + int originalReferenceCount = references.Count; + + + uint index = 0; + BlockingCollection referenceTargets = new BlockingCollection(); + + var task = Task.Run(() => { + + DateTime utcNow = DateTime.UtcNow; + + while (DateTime.UtcNow - utcNow < TimeSpan.FromSeconds(3)) + { + var target = new ExpandedNodeId(index++, "test.namespace"); + testNodeState.AddReference(ReferenceTypeIds.HasComponent, false, target); + referenceTargets.Add(target); + } + + referenceTargets.CompleteAdding(); + + }); + + while (referenceTargets.TryTake(out ExpandedNodeId target, TimeSpan.FromSeconds(1))) + { + var removeReferenceSuccess = testNodeState.RemoveReference(ReferenceTypeIds.HasComponent, false, target); + Assert.IsTrue(removeReferenceSuccess); + } + + task.Wait(); + + references.Clear(); + testNodeState.GetReferences(systemContext, references); + + Assert.AreEqual(originalReferenceCount, references.Count); + + } + + [Test] + public void NodeStateNotifiersCollectionConcurrencyTest() + { + var serviceMessageContext = new ServiceMessageContext(); + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + var testNodeState = new BaseObjectState(null); + + testNodeState.Create( + new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + List notifiers = new List(); + + testNodeState.GetNotifiers(systemContext, notifiers); + + int originalNotifierCount = notifiers.Count; + + + uint index = 0; + BlockingCollection notifierTargets = new BlockingCollection(); + + var task = Task.Run(() => { + + DateTime utcNow = DateTime.UtcNow; + + while (DateTime.UtcNow - utcNow < TimeSpan.FromSeconds(3)) + { + var targetState = new AnalogUnitRangeState(null); + + testNodeState.Create( + new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + var target = new ExpandedNodeId(index++, "test.namespace"); + testNodeState.AddNotifier(systemContext, ReferenceTypeIds.HasEventSource, false, targetState); + notifierTargets.Add(targetState); + } + + notifierTargets.CompleteAdding(); + + }); + + while (notifierTargets.TryTake(out NodeState target, TimeSpan.FromSeconds(1))) + { + testNodeState.RemoveNotifier(systemContext, target, false); + } + + task.Wait(); + + notifiers.Clear(); + testNodeState.GetNotifiers(systemContext, notifiers); + + Assert.AreEqual(originalNotifierCount, notifiers.Count); + + } + + [Test] + public void NodeStateChildrenCollectionConcurrencyTest() + { + var serviceMessageContext = new ServiceMessageContext(); + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + var testNodeState = new BaseObjectState(null); + + testNodeState.Create( + new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + List children = new List(); + + testNodeState.GetChildren(systemContext, children); + + int originalNotifierCount = children.Count; + + + uint index = 0; + BlockingCollection childrenCollection = new BlockingCollection(); + + var task = Task.Run(() => { + + DateTime utcNow = DateTime.UtcNow; + + while (DateTime.UtcNow - utcNow < TimeSpan.FromSeconds(3)) + { + var targetState = new AnalogUnitRangeState(null); + + testNodeState.Create( + new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + var target = new ExpandedNodeId(index++, "test.namespace"); + testNodeState.AddChild(targetState); + childrenCollection.Add(targetState); + } + + childrenCollection.CompleteAdding(); + + }); + + while (childrenCollection.TryTake(out BaseInstanceState child, TimeSpan.FromSeconds(1))) + { + testNodeState.RemoveChild(child); + } + + task.Wait(); + + children.Clear(); + testNodeState.GetChildren(systemContext, children); + + Assert.AreEqual(originalNotifierCount, children.Count); + + } + } +} diff --git a/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateHandlerConcurrencyTests.cs b/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateHandlerConcurrencyTests.cs new file mode 100644 index 0000000000..d7b6188595 --- /dev/null +++ b/Tests/Opc.Ua.Core.Tests/Stack/State/NodeStateHandlerConcurrencyTests.cs @@ -0,0 +1,524 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.Core.Tests.Stack.State +{ + /// + /// Tests for concurrency issues in BaseVariableState class + /// + [TestFixture] + [SetCulture("en-us"), SetUICulture("en-us")] + [Category("NodeStateConcurrency")] + [Parallelizable] + public class NodeStateHandlerConcurrencyTests + { + + // This data tests all handlers in NodeState and BaseVariableState class + public static IEnumerable VariableHandlerTestCases + { + get + { + // Test OnWriteValue + Action action = (BaseVariableState state) => { + state.OnWriteValue = ValueChangedHandler; + state.OnWriteValue = null; + }; + yield return new TestCaseData(Attributes.Value, new Variant(15.5), action); + + // Test OnSimpleWriteValue + action = (BaseVariableState state) => { + state.OnSimpleWriteValue = NodeAttributeEventHandler; + state.OnSimpleWriteValue = null; + }; + yield return new TestCaseData(Attributes.Value, new Variant(15.5), action); + + // Test OnWriteNodeId + action = (BaseVariableState state) => { + state.OnWriteNodeId = NodeAttributeEventHandler; + state.OnWriteNodeId = null; + }; + yield return new TestCaseData(Attributes.NodeId, new Variant(new NodeId(22, 7)), action); + + // Test OnWriteNodeClass + action = (BaseVariableState state) => { + state.OnWriteNodeClass = NodeAttributeEventHandler; + state.OnWriteNodeClass = null; + }; + yield return new TestCaseData(Attributes.NodeClass, new Variant((int)NodeClass.Variable), action); + + // Test OnWriteBrowseName + action = (BaseVariableState state) => { + state.OnWriteBrowseName = NodeAttributeEventHandler; + state.OnWriteBrowseName = null; + }; + yield return new TestCaseData(Attributes.BrowseName, new Variant(new QualifiedName("test")), action); + + // Test OnWriteDisplayName + action = (BaseVariableState state) => { + state.OnWriteDisplayName = NodeAttributeEventHandler; + state.OnWriteDisplayName = null; + }; + yield return new TestCaseData(Attributes.DisplayName, new Variant(new LocalizedText("test")), action); + + // Test OnWriteDescription + action = (BaseVariableState state) => { + state.OnWriteDescription = NodeAttributeEventHandler; + state.OnWriteDescription = null; + }; + yield return new TestCaseData(Attributes.Description, new Variant(new LocalizedText("test")), action); + + // Test OnWriteWriteMask + action = (BaseVariableState state) => { + state.OnWriteWriteMask = NodeAttributeEventHandler; + state.OnWriteWriteMask = null; + }; + yield return new TestCaseData(Attributes.WriteMask, new Variant((uint)AttributeWriteMask.WriteMask | (uint)AttributeWriteMask.UserWriteMask), action); + + // Test OnWriteUserWriteMask + action = (BaseVariableState state) => { + state.OnWriteUserWriteMask = NodeAttributeEventHandler; + state.OnWriteUserWriteMask = null; + }; + yield return new TestCaseData(Attributes.UserWriteMask, new Variant((uint)AttributeWriteMask.WriteMask | (uint)AttributeWriteMask.UserWriteMask), action); + + // Test OnWriteRolePermissions + action = (BaseVariableState state) => { + state.OnWriteRolePermissions = NodeAttributeEventHandler; + state.OnWriteRolePermissions = null; + }; + yield return new TestCaseData(Attributes.RolePermissions, new Variant(new ExtensionObject[] { }), action); + + // Test OnWriteAccessRestrictions + action = (BaseVariableState state) => { + state.OnWriteAccessRestrictions = NodeAttributeEventHandler; + state.OnWriteAccessRestrictions = null; + }; + yield return new TestCaseData(Attributes.AccessRestrictions, new Variant((ushort)AccessRestrictionType.EncryptionRequired), action); + + // Test OnWriteDataType + action = (BaseVariableState state) => { + state.OnWriteDataType = NodeAttributeEventHandler; + state.OnWriteDataType = null; + }; + yield return new TestCaseData(Attributes.DataType, new Variant(DataTypeIds.Double), action); + + // Test OnWriteValueRank + action = (BaseVariableState state) => { + state.OnWriteValueRank = NodeAttributeEventHandler; + state.OnWriteValueRank = null; + }; + yield return new TestCaseData(Attributes.ValueRank, new Variant(ValueRanks.Scalar), action); + + // Test OnWriteArrayDimensions + action = (BaseVariableState state) => { + state.OnWriteArrayDimensions = NodeAttributeEventHandler; + state.OnWriteArrayDimensions = null; + }; + yield return new TestCaseData(Attributes.ArrayDimensions, new Variant(new List() { 2, 2 }), action); + + // Test OnWriteAccessLevel + action = (BaseVariableState state) => { + state.OnWriteAccessLevel = NodeAttributeEventHandler; + state.OnWriteAccessLevel = null; + }; + yield return new TestCaseData(Attributes.AccessLevel, new Variant(AccessLevels.CurrentRead), action); + + // Test OnWriteUserAccessLevel + action = (BaseVariableState state) => { + state.OnWriteUserAccessLevel = NodeAttributeEventHandler; + state.OnWriteUserAccessLevel = null; + }; + yield return new TestCaseData(Attributes.UserAccessLevel, new Variant(AccessLevels.CurrentRead), action); + + // Test OnWriteMinimumSamplingInterval + action = (BaseVariableState state) => { + state.OnWriteMinimumSamplingInterval = NodeAttributeEventHandler; + state.OnWriteMinimumSamplingInterval = null; + }; + yield return new TestCaseData(Attributes.MinimumSamplingInterval, new Variant(1000.0), action); + + // Test OnWriteHistorizing + action = (BaseVariableState state) => { + state.OnWriteHistorizing = NodeAttributeEventHandler; + state.OnWriteHistorizing = null; + }; + yield return new TestCaseData(Attributes.Historizing, new Variant(true), action); + } + } + + // This test data test all handlers in BaseVariableTypeState and BaseTypeState class + public static IEnumerable VariableTypeHandlerTestCases + { + get + { + // Test OnWriteDataType + Action action = (BaseDataVariableTypeState state) => { + state.OnWriteDataType = NodeAttributeEventHandler; + state.OnWriteDataType = null; + }; + yield return new TestCaseData(Attributes.DataType, new Variant(DataTypeIds.Double), action); + + // Test OnWriteValueRank + action = (BaseDataVariableTypeState state) => { + state.OnWriteValueRank = NodeAttributeEventHandler; + state.OnWriteValueRank = null; + }; + yield return new TestCaseData(Attributes.ValueRank, new Variant(ValueRanks.Scalar), action); + + // Test OnWriteArrayDimensions + action = (BaseDataVariableTypeState state) => { + state.OnWriteArrayDimensions = NodeAttributeEventHandler; + state.OnWriteArrayDimensions = null; + }; + yield return new TestCaseData(Attributes.ArrayDimensions, new Variant(new List() { 2, 2 }), action); + + // Test OnWriteIsAbstract + action = (BaseDataVariableTypeState state) => { + state.OnWriteIsAbstract = NodeAttributeEventHandler; + state.OnWriteIsAbstract = null; + }; + yield return new TestCaseData(Attributes.IsAbstract, new Variant(false), action); + } + } + + // This test data test all handlers in BaseObjectState class + public static IEnumerable ObjectHandlerTestCases + { + get + { + // Test EventNotifier + Action action = (BaseObjectState state) => { + state.OnWriteEventNotifier = NodeAttributeEventHandler; + state.OnWriteEventNotifier = null; + }; + yield return new TestCaseData(Attributes.EventNotifier, new Variant(EventNotifiers.SubscribeToEvents), action); + + } + } + + // This test data test all handlers in MethodState class + public static IEnumerable MethodHandlerTestCases + { + get + { + // Test Executable + Action action = (MethodState state) => { + state.OnWriteExecutable = NodeAttributeEventHandler; + state.OnWriteExecutable = null; + }; + yield return new TestCaseData(Attributes.Executable, new Variant(true), action); + + // Test UserExecutable + action = (MethodState state) => { + state.OnWriteUserExecutable = NodeAttributeEventHandler; + state.OnWriteUserExecutable = null; + }; + yield return new TestCaseData(Attributes.UserExecutable, new Variant(true), action); + + } + } + + // This test data test all handlers in ReferenceTypeState class + public static IEnumerable ReferenceTypeHandlerTestCases + { + get + { + // Test InverseName + Action action = (ReferenceTypeState state) => { + state.OnWriteInverseName = NodeAttributeEventHandler; + state.OnWriteInverseName = null; + }; + yield return new TestCaseData(Attributes.InverseName, new Variant(new LocalizedText("inverse test")), action); + + // Test Symmetric + action = (ReferenceTypeState state) => { + state.OnWriteSymmetric = NodeAttributeEventHandler; + state.OnWriteSymmetric = null; + }; + yield return new TestCaseData(Attributes.Symmetric, new Variant(true), action); + + } + } + + // This test data test all handlers in ViewState class + public static IEnumerable ViewHandlerTestCases + { + get + { + // Test EventNotifier + Action action = (ViewState state) => { + state.OnWriteEventNotifier = NodeAttributeEventHandler; + state.OnWriteEventNotifier = null; + }; + yield return new TestCaseData(Attributes.EventNotifier, new Variant(EventNotifiers.SubscribeToEvents), action); + + // Test ContainsNoLoops + action = (ViewState state) => { + state.OnWriteContainsNoLoops = NodeAttributeEventHandler; + state.OnWriteContainsNoLoops = null; + }; + yield return new TestCaseData(Attributes.ContainsNoLoops, new Variant(true), action); + + } + } + + // This test data test all handlers in DataTypeState class + public static IEnumerable DataTypeHandlerTestCases + { + get + { + // Test DataTypeDefinition + Action action = (DataTypeState state) => { + state.OnWriteDataTypeDefinition = NodeAttributeEventHandler; + state.OnWriteDataTypeDefinition = null; + }; + yield return new TestCaseData(Attributes.DataTypeDefinition, new Variant(new ExtensionObject()), action); + + } + } + + [TestCaseSource(nameof(VariableHandlerTestCases))] + [Parallelizable] + public void VariableNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new AnalogUnitRangeState(null); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + [TestCaseSource(nameof(VariableTypeHandlerTestCases))] + [Parallelizable] + public void VariableTypeNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new BaseDataVariableTypeState(); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + [TestCaseSource(nameof(ObjectHandlerTestCases))] + [Parallelizable] + public void ObjectNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new BaseObjectState(null); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + [TestCaseSource(nameof(MethodHandlerTestCases))] + [Parallelizable] + public void MethodNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new MethodState(null); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + [TestCaseSource(nameof(ReferenceTypeHandlerTestCases))] + [Parallelizable] + public void ReferenceTypeNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new ReferenceTypeState(); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + [TestCaseSource(nameof(ViewHandlerTestCases))] + [Parallelizable] + public void ViewNodeHandlerConcurrencyTest( + uint attribute, + Variant variant, + Action concurrentTaskAction) + { + var testNodeState = new ViewState(); + var serviceMessageContext = new ServiceMessageContext(); + + var systemContext = new SystemContext() { NamespaceUris = serviceMessageContext.NamespaceUris }; + + testNodeState.Create( + systemContext, + new NodeId("TestNode", (ushort)7), + new QualifiedName("TestNode", (ushort)7), + new LocalizedText("TestNode"), + true); + + ExecuteNodeHandlerConcurrencyTest( + systemContext, + attribute, + variant, + testNodeState, + concurrentTaskAction); + + } + + private void ExecuteNodeHandlerConcurrencyTest( + ISystemContext systemContext, + uint attribute, + Variant variant, + T node, + Action concurrentTaskAction) where T : NodeState + { + + node.WriteMask = AttributeWriteMask.AccessLevel | AttributeWriteMask.ArrayDimensions | AttributeWriteMask.BrowseName | AttributeWriteMask.ContainsNoLoops | AttributeWriteMask.DataType | + AttributeWriteMask.Description | AttributeWriteMask.DisplayName | AttributeWriteMask.EventNotifier | AttributeWriteMask.Executable | AttributeWriteMask.Historizing | AttributeWriteMask.InverseName | AttributeWriteMask.IsAbstract | + AttributeWriteMask.MinimumSamplingInterval | AttributeWriteMask.NodeClass | AttributeWriteMask.NodeId | AttributeWriteMask.Symmetric | AttributeWriteMask.UserAccessLevel | AttributeWriteMask.UserExecutable | + AttributeWriteMask.UserWriteMask | AttributeWriteMask.ValueForVariableType | AttributeWriteMask.ValueRank | AttributeWriteMask.WriteMask | AttributeWriteMask.RolePermissions | AttributeWriteMask.AccessRestrictions; + + if(node is BaseVariableState baseVariableState) + { + // Make Value attribute writable so that it is possible to test Value attribute writing + var result = baseVariableState.WriteAttribute(systemContext, Attributes.AccessLevel, NumericRange.Empty, new DataValue(new Variant(AccessLevels.CurrentReadOrWrite))); + Assert.IsTrue(ServiceResult.IsGood(result)); + result = baseVariableState.WriteAttribute(systemContext, Attributes.UserAccessLevel, NumericRange.Empty, new DataValue(new Variant(AccessLevels.CurrentReadOrWrite))); + Assert.IsTrue(ServiceResult.IsGood(result)); + } + + bool running = true; + + var thread = new Thread(() => { + while (running) + { + concurrentTaskAction(node); + } + }); + + thread.Start(); + + DateTime utcNow = DateTime.UtcNow; + + while (DateTime.UtcNow - utcNow < TimeSpan.FromSeconds(1)) + { + var writeResult = node.WriteAttribute( + systemContext, + attribute, + NumericRange.Empty, + new DataValue(variant)); + + Assert.IsTrue(ServiceResult.IsGood(writeResult), "Expected Good ServiceResult but was: {0}", writeResult); + } + + running = false; + + thread.Join(); + } + + private static ServiceResult ValueChangedHandler( + ISystemContext context, + NodeState node, + NumericRange indexRange, + QualifiedName dataEncoding, + ref object value, + ref StatusCode statusCode, + ref DateTime timestamp) + { + return ServiceResult.Good; + } + + private static ServiceResult NodeAttributeEventHandler( + ISystemContext context, + NodeState node, + ref T value) + { + return ServiceResult.Good; + } + } + +}