fix(opcua): shelve declaration ids from a fixed set, not an address-space lookup; pin instance-id + state-machine-id shelve calls (#629)
New() ran FindNodeInAddressSpace under Lock, taking other node managers' locks (lock-order risk); the three AlarmConditionType_ShelvingState_* ids are now a static set. Adds wire tests: TimedShelve/Unshelve by the condition's own instance method ids (object = condition or its ShelvingState) succeed and report, and a ShelvedStateMachineType_* id call is pinned as BadMethodInvalid with a positive control. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -2499,6 +2499,20 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
_ => new AlarmConditionState(parent),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #629 — the <c>AlarmConditionType_ShelvingState_*</c> method instance declarations a client calls
|
||||
/// the shelve methods by. <see cref="New"/> records them as the methods' <c>MethodDeclarationId</c>
|
||||
/// (the SDK's own is the <c>ShelvedStateMachineType</c>'s), so calls by these ids keep resolving after
|
||||
/// the methods move to instance-scoped NodeIds. Calls by the <c>ShelvedStateMachineType_*</c> ids are
|
||||
/// not supported (pinned by <c>ConditionChildNodeIdTests</c>).
|
||||
/// </summary>
|
||||
private static readonly HashSet<NodeId> ShelvingMethodDeclarationIds =
|
||||
[
|
||||
MethodIds.AlarmConditionType_ShelvingState_TimedShelve,
|
||||
MethodIds.AlarmConditionType_ShelvingState_OneShotShelve,
|
||||
MethodIds.AlarmConditionType_ShelvingState_Unshelve,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #629 — the <see cref="INodeIdFactory"/> hook <c>NodeState.Create(…, assignNodeIds: true)</c> calls
|
||||
/// for the node being created and then, top-down, for every child it built from the type's definition.
|
||||
@@ -2543,15 +2557,10 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// already, over an SDK-generated ns=0 NodeId. The ShelvingState methods do NOT: their declaration id
|
||||
// is the ShelvedStateMachineType's (i=2948), and they were found only because their NodeId WAS the
|
||||
// AlarmConditionType instance declaration every client uses. Adopt that id as the declaration before
|
||||
// it is replaced — but only when it is a real node of the standard address space, never an
|
||||
// SDK-generated number — or every shelve/unshelve call fails BadMethodInvalid.
|
||||
if (node is MethodState method
|
||||
&& method.NodeId is { NamespaceIndex: 0 } declarationId
|
||||
&& declarationId != method.MethodDeclarationId
|
||||
&& FindNodeInAddressSpace(declarationId) is MethodState)
|
||||
{
|
||||
method.MethodDeclarationId = declarationId;
|
||||
}
|
||||
// it is replaced, or every shelve/unshelve call fails BadMethodInvalid. A fixed set, deliberately: a
|
||||
// lookup in the standard address space would take other node managers' locks while we hold Lock.
|
||||
if (node is MethodState method && ShelvingMethodDeclarationIds.Contains(method.NodeId))
|
||||
method.MethodDeclarationId = method.NodeId;
|
||||
|
||||
return new NodeId($"{parentId.Identifier}/{node.BrowseName.Name}", parentId.NamespaceIndex);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user