mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-21 09:06:37 +08:00
b3518b53df
# Description
This pull request introduces a scalable consumer task model for the
`MonitoredNode2` class, optimizing the handling of event notifications
for notifier nodes. Any notifier node can opt into (or out of) dynamic
scaling of consumer tasks based on the number of event monitored items,
improving throughput and responsiveness under high event loads. The
Server node (`ObjectIds.Server`) is always auto-opted-in. The
implementation includes safe scaling up and down of consumers, robust
disposal logic, Fluent API integration, and tests to verify correct
delivery of events.
**Scalable consumer task management:**
* Added an `enableMultipleEventConsumers` constructor parameter to
`MonitoredNode2` that enables dynamic scaling of consumer tasks to match
the number of event monitored items. The Server node
(`ObjectIds.Server`) always auto-opts-in regardless of this parameter.
* Added `AllowMultipleEventConsumers(NodeId, bool enable = true)`
protected method on `AsyncCustomNodeManager` with an internal
`MultiConsumerNodeIds` registry, so any node manager can
programmatically opt in or out specific nodes.
* Added `IsMultipleEventConsumerNode(NodeId)` to the `IAsyncNodeManager`
interface, allowing consumers to query multi-consumer opt-in status
through the interface without casting to `AsyncCustomNodeManager`.
* Both `MonitoredNodeMonitoredItemManager` and
`SamplingGroupMonitoredItemManager` use the `IAsyncNodeManager`
interface method when creating `MonitoredNode2` instances.
**Fluent API integration:**
* Added `AllowMultipleEventConsumers(bool enable = true)` method to
`INodeBuilder` and its `NodeBuilder` implementation, allowing any
notifier node to be opted in or out via the fluent builder pipeline
(e.g. `builder.Node("myNotifier").AllowMultipleEventConsumers()`).
* The `NodeManagerBuilder` routes the registration to the owning
`AsyncCustomNodeManager`'s multi-consumer registry, supporting both
opt-in and opt-out.
**Testing and verification:**
* Added unit tests to verify multi-consumer behavior for both Server
node (auto-opt-in) and non-Server nodes (explicit opt-in), as well as
default single-consumer behavior for nodes without opt-in.
* Fixed race condition in permission cache tests
(`OnMonitoredNodeChanged_DefaultPermissionsChanged_CacheInvalidated` and
`InvalidatePermissionCacheForSession` tests) that caused flaky CI
failures. The tests now signal on `QueueValue` (which fires after the
cache is populated) instead of `ValidateRolePermissionsAsync` (which
fires before), ensuring cache invalidation operates on a populated
cache.
## Related Issues
## Checklist
- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
792 lines
29 KiB
C#
792 lines
29 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Opc.Ua.Server
|
|
{
|
|
/// <summary>
|
|
/// The factory for an <see cref="AsyncNodeManagerAdapter"/>
|
|
/// </summary>
|
|
public static class AsyncNodeManagerAdapterFactory
|
|
{
|
|
/// <summary>
|
|
/// returns an instance of <see cref="IAsyncNodeManager"/>
|
|
/// if the NodeManager does not implement the interface uses the <see cref="AsyncNodeManagerAdapter"/>
|
|
/// to create an IAsyncNodeManager compatible object
|
|
/// </summary>
|
|
public static IAsyncNodeManager ToAsyncNodeManager(this INodeManager nodeManager)
|
|
{
|
|
if (nodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager;
|
|
}
|
|
return new AsyncNodeManagerAdapter(nodeManager);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An adapter that makes a synchronous INodeManager conform to the IAsyncNodeManager interface.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This allows synchronous, or only partially asynchronous node managers to be treated as asynchronous, which can help
|
|
/// unify the calling logic within the MasterNodeManager.
|
|
/// </remarks>
|
|
public class AsyncNodeManagerAdapter : IAsyncNodeManager, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="AsyncNodeManagerAdapter"/> class.
|
|
/// </summary>
|
|
/// <param name="nodeManager">The synchronous node manager to wrap.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="nodeManager"/> is <c>null</c>.</exception>
|
|
public AsyncNodeManagerAdapter(INodeManager nodeManager)
|
|
{
|
|
SyncNodeManager = nodeManager ?? throw new ArgumentNullException(nameof(nodeManager));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IEnumerable<string> NamespaceUris => SyncNodeManager.NamespaceUris;
|
|
|
|
/// <inheritdoc/>
|
|
public INodeManager SyncNodeManager { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask AddReferencesAsync(
|
|
IDictionary<NodeId, IList<IReference>> references,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.AddReferencesAsync(references, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.AddReferences(references);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ContinuationPoint?> BrowseAsync(
|
|
OperationContext context,
|
|
ContinuationPoint continuationPoint,
|
|
IList<ReferenceDescription> references,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IBrowseAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.BrowseAsync(context, continuationPoint, references, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.Browse(context, ref continuationPoint, references);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<ContinuationPoint?>(continuationPoint);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask CallAsync(
|
|
OperationContext context,
|
|
ArrayOf<CallMethodRequest> methodsToCall,
|
|
IList<CallMethodResult> results,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is ICallAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.CallAsync(context, methodsToCall, results, errors, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.Call(context, methodsToCall, results, errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<MethodState> FindMethodStateAsync(
|
|
OperationContext context,
|
|
CallMethodRequest methodToCall,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is INodeManager3 nodeManager)
|
|
{
|
|
MethodState method = nodeManager.FindMethodState(context, methodToCall);
|
|
return new ValueTask<MethodState>(method);
|
|
}
|
|
|
|
return new ValueTask<MethodState>(result: null!);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> ConditionRefreshAsync(
|
|
OperationContext context,
|
|
IList<IEventMonitoredItem> monitoredItems,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IConditionRefreshAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.ConditionRefreshAsync(context, monitoredItems, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.ConditionRefresh(context, monitoredItems);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask CreateAddressSpaceAsync(
|
|
IDictionary<NodeId, IList<IReference>> externalReferences,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.CreateAddressSpaceAsync(externalReferences, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.CreateAddressSpace(externalReferences);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask CreateMonitoredItemsAsync(
|
|
OperationContext context,
|
|
uint subscriptionId,
|
|
double publishingInterval,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<MonitoredItemCreateRequest> itemsToCreate,
|
|
IList<ServiceResult> errors,
|
|
IList<MonitoringFilterResult> filterErrors,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
bool createDurable,
|
|
MonitoredItemIdFactory monitoredItemIdFactory,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is ICreateMonitoredItemsAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.CreateMonitoredItemsAsync(
|
|
context,
|
|
subscriptionId,
|
|
publishingInterval,
|
|
timestampsToReturn,
|
|
itemsToCreate,
|
|
errors,
|
|
filterErrors,
|
|
monitoredItems,
|
|
createDurable,
|
|
monitoredItemIdFactory,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.CreateMonitoredItems(
|
|
context,
|
|
subscriptionId,
|
|
publishingInterval,
|
|
timestampsToReturn,
|
|
itemsToCreate,
|
|
errors,
|
|
filterErrors,
|
|
monitoredItems,
|
|
createDurable,
|
|
monitoredItemIdFactory);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask DeleteAddressSpaceAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.DeleteAddressSpaceAsync(cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.DeleteAddressSpace();
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask DeleteMonitoredItemsAsync(
|
|
OperationContext context,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
IList<bool> processedItems,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IDeleteMonitoredItemsAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.DeleteMonitoredItemsAsync(
|
|
context,
|
|
monitoredItems,
|
|
processedItems,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.DeleteMonitoredItems(context, monitoredItems, processedItems, errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> DeleteReferenceAsync(
|
|
object sourceHandle,
|
|
NodeId referenceTypeId,
|
|
bool isInverse,
|
|
ExpandedNodeId targetId,
|
|
bool deleteBidirectional,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.DeleteReferenceAsync(sourceHandle,
|
|
referenceTypeId,
|
|
isInverse,
|
|
targetId,
|
|
deleteBidirectional,
|
|
cancellationToken);
|
|
}
|
|
|
|
ServiceResult result = SyncNodeManager.DeleteReference(sourceHandle,
|
|
referenceTypeId,
|
|
isInverse,
|
|
targetId,
|
|
deleteBidirectional);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<ServiceResult>(result);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<object> GetManagerHandleAsync(NodeId nodeId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.GetManagerHandleAsync(nodeId, cancellationToken);
|
|
}
|
|
|
|
object? handle = SyncNodeManager.GetManagerHandle(nodeId);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<object>(handle!);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<NodeMetadata> GetNodeMetadataAsync(
|
|
OperationContext context,
|
|
object targetHandle,
|
|
BrowseResultMask resultMask,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.GetNodeMetadataAsync(context, targetHandle, resultMask, cancellationToken);
|
|
}
|
|
|
|
NodeMetadata? nodeMetadata = SyncNodeManager.GetNodeMetadata(context, targetHandle, resultMask);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<NodeMetadata>(nodeMetadata!);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<NodeMetadata?> GetPermissionMetadataAsync(
|
|
OperationContext context,
|
|
object targetHandle,
|
|
BrowseResultMask resultMask,
|
|
Dictionary<NodeId, Variant[]> uniqueNodesServiceAttributesCache,
|
|
bool permissionsOnly,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.GetPermissionMetadataAsync(
|
|
context,
|
|
targetHandle,
|
|
resultMask,
|
|
uniqueNodesServiceAttributesCache,
|
|
permissionsOnly,
|
|
cancellationToken);
|
|
}
|
|
if (SyncNodeManager is INodeManager2 nodeManager2)
|
|
{
|
|
NodeMetadata? nodeMetadata = nodeManager2.GetPermissionMetadata(
|
|
context,
|
|
targetHandle,
|
|
resultMask,
|
|
uniqueNodesServiceAttributesCache,
|
|
permissionsOnly);
|
|
return new ValueTask<NodeMetadata?>(nodeMetadata);
|
|
}
|
|
|
|
return new ValueTask<NodeMetadata?>((NodeMetadata?)null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HistoryReadAsync(
|
|
OperationContext context,
|
|
HistoryReadDetails details,
|
|
TimestampsToReturn timestampsToReturn,
|
|
bool releaseContinuationPoints,
|
|
ArrayOf<HistoryReadValueId> nodesToRead,
|
|
IList<HistoryReadResult> results,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IHistoryReadAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.HistoryReadAsync(
|
|
context,
|
|
details,
|
|
timestampsToReturn,
|
|
releaseContinuationPoints,
|
|
nodesToRead,
|
|
results,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.HistoryRead(
|
|
context,
|
|
details,
|
|
timestampsToReturn,
|
|
releaseContinuationPoints,
|
|
nodesToRead,
|
|
results,
|
|
errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask HistoryUpdateAsync(
|
|
OperationContext context,
|
|
Type detailsType,
|
|
ArrayOf<HistoryUpdateDetails> nodesToUpdate,
|
|
IList<HistoryUpdateResult> results,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IHistoryUpdateAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.HistoryUpdateAsync(
|
|
context,
|
|
detailsType,
|
|
nodesToUpdate,
|
|
results,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.HistoryUpdate(
|
|
context,
|
|
detailsType,
|
|
nodesToUpdate,
|
|
results,
|
|
errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<bool> IsNodeInViewAsync(
|
|
OperationContext context,
|
|
NodeId viewId,
|
|
object nodeHandle,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.IsNodeInViewAsync(context, viewId, nodeHandle, cancellationToken);
|
|
}
|
|
|
|
if (SyncNodeManager is INodeManager2 nodeManager2)
|
|
{
|
|
bool result = nodeManager2.IsNodeInView(context, viewId, nodeHandle);
|
|
return new ValueTask<bool>(result);
|
|
}
|
|
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask ModifyMonitoredItemsAsync(OperationContext context,
|
|
TimestampsToReturn timestampsToReturn,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
ArrayOf<MonitoredItemModifyRequest> itemsToModify,
|
|
IList<ServiceResult> errors,
|
|
IList<MonitoringFilterResult> filterErrors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IModifyMonitoredItemsAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.ModifyMonitoredItemsAsync(
|
|
context,
|
|
timestampsToReturn,
|
|
monitoredItems,
|
|
itemsToModify,
|
|
errors,
|
|
filterErrors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.ModifyMonitoredItems(
|
|
context,
|
|
timestampsToReturn,
|
|
monitoredItems,
|
|
itemsToModify,
|
|
errors,
|
|
filterErrors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask ReadAsync(OperationContext context,
|
|
double maxAge,
|
|
ArrayOf<ReadValueId> nodesToRead,
|
|
IList<DataValue> values,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IReadAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.ReadAsync(
|
|
context,
|
|
maxAge,
|
|
nodesToRead,
|
|
values,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.Read(context, maxAge, nodesToRead, values, errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask RestoreMonitoredItemsAsync(
|
|
IList<IStoredMonitoredItem> itemsToRestore,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
IUserIdentity savedOwnerIdentity,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.RestoreMonitoredItemsAsync(itemsToRestore, monitoredItems, savedOwnerIdentity, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.RestoreMonitoredItems(itemsToRestore, monitoredItems, savedOwnerIdentity);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask SessionClosingAsync(
|
|
OperationContext context,
|
|
NodeId sessionId,
|
|
bool deleteSubscriptions,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.SessionClosingAsync(context, sessionId, deleteSubscriptions, cancellationToken);
|
|
}
|
|
|
|
if (SyncNodeManager is INodeManager2 nodeManager2)
|
|
{
|
|
nodeManager2.SessionClosing(context, sessionId, deleteSubscriptions);
|
|
}
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask SessionActivatedAsync(
|
|
OperationContext context,
|
|
NodeId sessionId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.SessionActivatedAsync(context, sessionId, cancellationToken);
|
|
}
|
|
|
|
if (SyncNodeManager is INodeManager3 nodeManager3)
|
|
{
|
|
nodeManager3.SessionActivated(context, sessionId);
|
|
}
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask SetMonitoringModeAsync(
|
|
OperationContext context,
|
|
MonitoringMode monitoringMode,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
IList<bool> processedItems,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is ISetMonitoringModeAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.SetMonitoringModeAsync(
|
|
context,
|
|
monitoringMode,
|
|
monitoredItems,
|
|
processedItems,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.SetMonitoringMode(
|
|
context,
|
|
monitoringMode,
|
|
monitoredItems,
|
|
processedItems,
|
|
errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> SubscribeToAllEventsAsync(
|
|
OperationContext context,
|
|
uint subscriptionId,
|
|
IEventMonitoredItem monitoredItem,
|
|
bool unsubscribe,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.SubscribeToAllEventsAsync(
|
|
context,
|
|
subscriptionId,
|
|
monitoredItem,
|
|
unsubscribe,
|
|
cancellationToken);
|
|
}
|
|
|
|
ServiceResult result = SyncNodeManager.SubscribeToAllEvents(
|
|
context,
|
|
subscriptionId,
|
|
monitoredItem,
|
|
unsubscribe);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<ServiceResult>(result);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> SubscribeToEventsAsync(
|
|
OperationContext context,
|
|
object sourceId,
|
|
uint subscriptionId,
|
|
IEventMonitoredItem monitoredItem,
|
|
bool unsubscribe,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.SubscribeToEventsAsync(
|
|
context,
|
|
sourceId,
|
|
subscriptionId,
|
|
monitoredItem,
|
|
unsubscribe,
|
|
cancellationToken);
|
|
}
|
|
|
|
ServiceResult result = SyncNodeManager.SubscribeToEvents(
|
|
context,
|
|
sourceId,
|
|
subscriptionId,
|
|
monitoredItem,
|
|
unsubscribe);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return new ValueTask<ServiceResult>(result);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask TransferMonitoredItemsAsync(
|
|
OperationContext context,
|
|
bool sendInitialValues,
|
|
IList<IMonitoredItem> monitoredItems,
|
|
IList<bool> processedItems,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is ITransferMonitoredItemsAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.TransferMonitoredItemsAsync(
|
|
context,
|
|
sendInitialValues,
|
|
monitoredItems,
|
|
processedItems,
|
|
errors,
|
|
cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.TransferMonitoredItems(
|
|
context,
|
|
sendInitialValues,
|
|
monitoredItems,
|
|
processedItems,
|
|
errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask TranslateBrowsePathAsync(
|
|
OperationContext context,
|
|
object sourceHandle,
|
|
RelativePathElement relativePath,
|
|
IList<ExpandedNodeId> targetIds,
|
|
IList<NodeId> unresolvedTargetIds,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is ITranslateBrowsePathAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.TranslateBrowsePathAsync(context, sourceHandle, relativePath, targetIds, unresolvedTargetIds, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.TranslateBrowsePath(context, sourceHandle, relativePath, targetIds, unresolvedTargetIds);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask WriteAsync(
|
|
OperationContext context,
|
|
ArrayOf<WriteValue> nodesToWrite,
|
|
IList<ServiceResult> errors,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IWriteAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.WriteAsync(context, nodesToWrite, errors, cancellationToken);
|
|
}
|
|
|
|
SyncNodeManager.Write(context, nodesToWrite, errors);
|
|
|
|
// Return a completed ValueTask since the underlying call is synchronous.
|
|
return default;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> ValidateEventRolePermissionsAsync(
|
|
IEventMonitoredItem monitoredItem,
|
|
IFilterTarget filterTarget,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.ValidateEventRolePermissionsAsync(monitoredItem, filterTarget, cancellationToken);
|
|
}
|
|
|
|
if (SyncNodeManager is INodeManager3 nodeManager2)
|
|
{
|
|
return new ValueTask<ServiceResult>(nodeManager2.ValidateEventRolePermissions(monitoredItem, filterTarget));
|
|
}
|
|
|
|
return new ValueTask<ServiceResult>(ServiceResult.Good);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<ServiceResult> ValidateRolePermissionsAsync(
|
|
OperationContext operationContext,
|
|
NodeId nodeId,
|
|
PermissionType requestedPermission,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
|
|
{
|
|
return asyncNodeManager.ValidateRolePermissionsAsync(operationContext, nodeId, requestedPermission, cancellationToken);
|
|
}
|
|
|
|
if (SyncNodeManager is INodeManager3 nodeManager2)
|
|
{
|
|
return new ValueTask<ServiceResult>(nodeManager2.ValidateRolePermissions(operationContext, nodeId, requestedPermission));
|
|
}
|
|
|
|
return new ValueTask<ServiceResult>(ServiceResult.Good);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsMultipleEventConsumerNode(NodeId nodeId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Frees any unmanaged resources.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An overrideable version of the Dispose.
|
|
/// </summary>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
(SyncNodeManager as IDisposable)?.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|