Skip to content

Commit 346aaf5

Browse files
committed
cleanup and fixes
1 parent 19b2e05 commit 346aaf5

File tree

8 files changed

+72
-42
lines changed

8 files changed

+72
-42
lines changed

.github/agents/opcua-v16-migration.agent.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ Also: `ReadOnlyList<T>` → `ArrayOf<T>`, `IList<T>` parameters → `ArrayOf<T>`
173173
- Use `List<T>` when items are added/removed/modified, then convert to `ArrayOf<T>` with `.ToArrayOf()`.
174174
- `ArrayOf<T>` implicitly converts from `List<T>` but not vice versa. Use `.ToList()` to convert back.
175175
- `ArrayOf<T>` supports collection expressions: `ArrayOf<int> arr = [1, 2, 3];`
176+
- To follow best coding practices Do NOT use casts to create the ArrayOf but use `.ToArrayOf()` or create it directly using a collection expression
176177

177178
### ArrayOf<T> Key API
178179

Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -262,29 +262,26 @@ protected ServiceResult OnGetMonitoredItems(
262262
return StatusCodes.BadInvalidArgument;
263263
}
264264

265-
foreach (ISubscription subscription in Server.SubscriptionManager.GetSubscriptions())
265+
if (!Server.SubscriptionManager.TryGetSubscription(subscriptionId, out ISubscription subscription))
266266
{
267-
if (subscription.Id == subscriptionId)
268-
{
269-
if (context is ISessionSystemContext session &&
270-
subscription.SessionId != session.SessionId)
271-
{
272-
// user tries to access subscription of different session
273-
return StatusCodes.BadUserAccessDenied;
274-
}
267+
return StatusCodes.BadSubscriptionIdInvalid;
268+
}
275269

276-
subscription.GetMonitoredItems(
277-
out ArrayOf<uint> serverHandles,
278-
out ArrayOf<uint> clientHandles);
270+
if (context is ISessionSystemContext session &&
271+
subscription.SessionId != session.SessionId)
272+
{
273+
// user tries to access subscription of different session
274+
return StatusCodes.BadUserAccessDenied;
275+
}
279276

280-
outputArguments[0] = serverHandles;
281-
outputArguments[1] = clientHandles;
277+
subscription.GetMonitoredItems(
278+
out ArrayOf<uint> serverHandles,
279+
out ArrayOf<uint> clientHandles);
282280

283-
return ServiceResult.Good;
284-
}
285-
}
281+
outputArguments[0] = serverHandles;
282+
outputArguments[1] = clientHandles;
286283

287-
return StatusCodes.BadSubscriptionIdInvalid;
284+
return ServiceResult.Good;
288285
}
289286

290287
/// <summary>
@@ -306,24 +303,21 @@ protected ServiceResult OnResendData(
306303
return StatusCodes.BadInvalidArgument;
307304
}
308305

309-
foreach (ISubscription subscription in Server.SubscriptionManager.GetSubscriptions())
306+
if (!Server.SubscriptionManager.TryGetSubscription(subscriptionId, out ISubscription subscription))
310307
{
311-
if (subscription.Id == subscriptionId)
312-
{
313-
if (context is not ServerSystemContext session ||
314-
subscription.SessionId != session.SessionId)
315-
{
316-
// user tries to access subscription of different session
317-
return StatusCodes.BadUserAccessDenied;
318-
}
319-
320-
subscription.ResendData(session.OperationContext);
308+
return StatusCodes.BadSubscriptionIdInvalid;
309+
}
321310

322-
return ServiceResult.Good;
323-
}
311+
if (context is not ServerSystemContext session ||
312+
subscription.SessionId != session.SessionId)
313+
{
314+
// user tries to access subscription of different session
315+
return StatusCodes.BadUserAccessDenied;
324316
}
325317

326-
return StatusCodes.BadSubscriptionIdInvalid;
318+
subscription.ResendData(session.OperationContext);
319+
320+
return ServiceResult.Good;
327321
}
328322

329323
/// <summary>

Libraries/Opc.Ua.Server/Subscription/ISubscriptionManager.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ public interface ISubscriptionManager : IDisposable
5858
/// <returns>A list of the subscriptions.</returns>
5959
IList<ISubscription> GetSubscriptions();
6060

61+
/// <summary>
62+
/// Get the subscription with the specified id
63+
/// </summary>
64+
/// <param name="id">The id of the subscription</param>
65+
/// <param name="subscription">The subscription if found else null</param>
66+
/// <returns>True if found</returns>
67+
bool TryGetSubscription(uint id, out ISubscription subscription);
68+
6169
/// <summary>
6270
/// Set a subscription into durable mode
6371
/// </summary>

Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,19 @@ public event SubscriptionEventHandler SubscriptionDeleted
181181
}
182182
}
183183

184-
/// <summary>
185-
/// Returns all of the subscriptions known to the subscription manager.
186-
/// </summary>
187-
/// <returns>A list of the subscriptions.</returns>
184+
185+
/// <inheritdoc/>
188186
public IList<ISubscription> GetSubscriptions()
189187
{
190188
return [.. m_subscriptions.Values];
191189
}
192190

191+
/// <inheritdoc/>
192+
public bool TryGetSubscription(uint id, out ISubscription subscription)
193+
{
194+
return m_subscriptions.TryGetValue(id, out subscription);
195+
}
196+
193197
/// <summary>
194198
/// Raises an event related to a subscription.
195199
/// </summary>

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ More samples based on the official [Nuget](https://www.nuget.org/packages/OPCFou
3737
* Source generator generated code behind during build
3838
* See [MigrationGuide](Docs/MigrationGuide.md) for details.
3939
* New AsyncCustomNodeManager (successor of CustomNodeManager2) with improved Locking Strategy, see [Server Async (TAP) Support](Docs/AsyncServerSupport.md)
40+
* In our Load Test the Server shows at least 2.5x higher throughput under load with 750 subscriptions totaling 450k Monitored items
41+
and write times for 600 items below 5 seconds were before > 10 seconds were needed. Also for event at least 3x faster event reporting was observed.
4042

4143
#### **New in 1.05.378**
4244

Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,23 @@ protected virtual void Dispose(bool disposing)
16691669

16701670
m_queue.Writer.Complete();
16711671

1672+
// Wait for all worker threads to complete
1673+
Task[] workerTasks;
1674+
lock (m_workers)
1675+
{
1676+
workerTasks = [.. m_workers];
1677+
}
1678+
1679+
try
1680+
{
1681+
Task.WaitAll(workerTasks, TimeSpan.FromSeconds(5));
1682+
}
1683+
catch (AggregateException)
1684+
{
1685+
// Ignore exceptions during shutdown
1686+
}
1687+
1688+
// Now drain any remaining requests from the queue
16721689
while (m_queue.Reader.TryRead(out IEndpointIncomingRequest request))
16731690
{
16741691
request.OperationCompleted(null, StatusCodes.BadServerHalted);

Tests/Opc.Ua.Server.Tests/DiagnosticsNodeManagerTests.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ public async Task GetMonitoredItems_ValidatesSessionAndReturnsItemsAsync()
206206
ArrayOf<uint> clientHandles = [3, 4];
207207
subMock.Setup(s => s.GetMonitoredItems(out serverHandles, out clientHandles));
208208

209-
m_subscriptionManagerMock.Setup(m => m.GetSubscriptions()).Returns([subMock.Object]);
209+
ISubscription outSub = subMock.Object;
210+
m_subscriptionManagerMock.Setup(m => m.TryGetSubscription(It.IsAny<uint>(), out outSub)).Returns(true);
210211

211212
using var manager = new DiagnosticsNodeManager(m_serverMock.Object, config, NullLogger.Instance);
212213
var externalRefs = new Dictionary<NodeId, IList<IReference>>();
@@ -247,7 +248,8 @@ public async Task GetMonitoredItems_InvalidSubscriptionId_ReturnsBadSubscription
247248
var config = new ApplicationConfiguration { ServerConfiguration = new ServerConfiguration() };
248249
SetupServerMock();
249250

250-
m_subscriptionManagerMock.Setup(m => m.GetSubscriptions()).Returns(System.Array.Empty<ISubscription>());
251+
ISubscription outSub = null;
252+
m_subscriptionManagerMock.Setup(m => m.TryGetSubscription(It.IsAny<uint>(), out outSub)).Returns(false);
251253

252254
using var manager = new DiagnosticsNodeManager(m_serverMock.Object, config, NullLogger.Instance);
253255
var externalRefs = new Dictionary<NodeId, IList<IReference>>();
@@ -279,7 +281,8 @@ public async Task ResendData_ValidatesAccessAndCallsResendDataAsync()
279281
subMock.Setup(s => s.SessionId).Returns(new NodeId(1, 1));
280282
subMock.Setup(s => s.ResendData(It.IsAny<OperationContext>()));
281283

282-
m_subscriptionManagerMock.Setup(m => m.GetSubscriptions()).Returns([subMock.Object]);
284+
ISubscription outSub = subMock.Object;
285+
m_subscriptionManagerMock.Setup(m => m.TryGetSubscription(It.IsAny<uint>(), out outSub)).Returns(true);
283286

284287
using var manager = new DiagnosticsNodeManager(m_serverMock.Object, config, NullLogger.Instance);
285288
var externalRefs = new Dictionary<NodeId, IList<IReference>>();
@@ -324,7 +327,8 @@ public async Task ResendData_InvalidSubscriptionId_ReturnsBadSubscriptionIdInval
324327
var config = new ApplicationConfiguration { ServerConfiguration = new ServerConfiguration() };
325328
SetupServerMock();
326329

327-
m_subscriptionManagerMock.Setup(m => m.GetSubscriptions()).Returns(System.Array.Empty<ISubscription>());
330+
ISubscription outSub = null;
331+
m_subscriptionManagerMock.Setup(m => m.TryGetSubscription(It.IsAny<uint>(), out outSub)).Returns(false);
328332

329333
using var manager = new DiagnosticsNodeManager(m_serverMock.Object, config, NullLogger.Instance);
330334
var externalRefs = new Dictionary<NodeId, IList<IReference>>();

Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,10 +739,10 @@ public async Task ServerEventSubscribeTestAsync()
739739
Assert.IsFalse(eventFields[2].IsNull); // SourceNode
740740
Assert.IsFalse(eventFields[3].IsNull); // SourceName
741741
Assert.IsFalse(eventFields[4].IsNull); // Time
742-
eventFields[5].TryGet(out LocalizedText receivedMessage); // Message
742+
Assert.That(eventFields[5].TryGet(out LocalizedText receivedMessage), Is.True); // Message
743743
Assert.IsFalse(receivedMessage.IsNull);
744744
Assert.AreEqual(eventMessage, receivedMessage.Text);
745-
eventFields[6].TryGet(out ushort receiveSeverity);
745+
Assert.That(eventFields[6].TryGet(out ushort receiveSeverity), Is.True);
746746
Assert.AreEqual((ushort)EventSeverity.Medium, receiveSeverity); // Severity
747747

748748
// Delete subscription

0 commit comments

Comments
 (0)