mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 23:38:39 +08:00
Q4 and Q5 of the request-queue comparison with ASP.NET Core/Kestrel (Q1 is #4914). Two commits, one per item. ## Q4: stop changing the process ThreadPool `RequestQueue` called `ThreadPool.SetMinThreads` / `SetMaxThreads` for the whole process every time a queue was created, including a throwaway 10-worker queue that the `ServerBase` constructor built and `InitializeRequestQueue` disposed again at start. - The calls are removed. Nothing in the repo (src, tests, samples) depends on them. Queue workers are asynchronous and do not hold a pool thread while they wait. `SetMaxThreads` never had an effect, because the default maximum (32767) is always above the configured counts. - The constructor no longer builds the throwaway queue. Until the server starts, `ServerBase.ScheduleIncomingRequest` completes a request with `BadServerHalted`. That request used to reach `ProcessRequestAsync` of a server that had not started yet. ### Release note (behaviour change) > The server no longer raises the process-wide thread pool minimum to `MinRequestThreadCount` (default 10). On machines with fewer logical processors than `MinRequestThreadCount`, the thread pool now starts with its own default minimum (the processor count). Hosts whose node managers block threads synchronously, and that relied on the old minimum, can set it themselves with `ThreadPool.SetMinThreads` or the `System.Threading.ThreadPool.MinThreads` runtime setting. ### Test hosts CI showed that test hosts depended on the old minimum. On the small linux runners, tests that block pool threads in parallel left a queued request waiting more than 5 s for a thread (`RequestQueueTests`, `RequestLifetimeTests`). `tests/TestThreadPoolSetup.cs` is an assembly-wide NUnit `SetUpFixture` that `tests/Directory.Build.targets` compiles into every NUnit test project. It restores the minimum of at least 10 worker and I/O threads, for the test hosts only. ## Q5: request queue metrics The SharedOnly path only logged `RequestQueueFull` (with a `// TODO: make a metric`). The queue now publishes instruments in both the shared and the isolation mode. They follow the style of `DefaultServerResourceIsolationProvider` and are modelled on Kestrel's `kestrel.queued_requests`, `http.server.active_requests` and `aspnetcore.rate_limiting.request.time_in_queue`. The meter comes from the server's `ITelemetryContext` (`CreateMeter()`, named after `Opc.Ua.Core`) and is disposed once shutdown has drained the queue. | Instrument | Type | |---|---| | `opcua.server.request_queue.queued` | observable gauge | | `opcua.server.request_queue.executing` | observable gauge | | `opcua.server.request_queue.parked` | observable gauge | | `opcua.server.request_queue.time_in_queue` | histogram, seconds | | `opcua.server.request_queue.rejections` | counter, tag `status` | Cost on the request path: - The gauges are observed from counters that already exist, so they add no work per request. - The time in queue uses a `Stopwatch` timestamp taken at enqueue, and only while the histogram has a listener, on both paths. The fair queue stamps an `Entry` only after its admission succeeds. Once the Q2 enqueue/received timestamp lands, it can replace this one. - Rejections are tagged with the interned symbolic id of the status code through the single-tag `Add` overload, so no tag array is allocated. - Rejections include queue full, halted, cancelled while queued, failed revalidation and every fair-admission rejection, because the fair queue's completion callback now goes through `RejectRequest`. Handler failures are not rejections. `docs/ServerScalability.md` lists the instruments. ## Tests - `RequestQueueTests`: request before start is halted, ThreadPool limits unchanged, shared queue metrics (queued, executing, parked, rejections by status, time in queue), parked gauge, fair queue metrics. - `Opc.Ua.Core.Tests` `Stack.Server`: 1946 passed, 0 failed (net10.0). - `Opc.Ua.Server.Tests`: 9653 passed. The 8 failures are symlink tests (`FileLinksRejectReadWriteAndCopy` and similar) that fail on this machine with "insufficient privileges" and are unrelated to this change. - net48 build of `Opc.Ua.Core.Tests` compiles. Conflicts to expect: the Q2/Q3 session (time in queue against TimeoutHint, retry-after) touches the dequeue and reject spots of `ServerBase.RequestQueue.cs`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com>
60 lines
2.5 KiB
C#
60 lines
2.5 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.Threading;
|
|
using NUnit.Framework;
|
|
|
|
#pragma warning disable CA1050 // A SetUpFixture outside any namespace applies to the whole test assembly.
|
|
|
|
/// <summary>
|
|
/// Raises the thread pool minimum of the test host for every test assembly.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The server no longer changes the process-wide thread pool. Up to 2.0.1 every
|
|
/// <c>ServerBase</c> raised the minimum to at least its <c>MinRequestThreadCount</c>
|
|
/// (10 by default), and test hosts on small CI runners depend on that minimum: tests
|
|
/// running in parallel block pool threads, and a queued request could otherwise wait
|
|
/// seconds for the thread pool to inject a thread.
|
|
/// </remarks>
|
|
[SetUpFixture]
|
|
public sealed class TestThreadPoolSetup
|
|
{
|
|
private const int kMinThreads = 10;
|
|
|
|
[OneTimeSetUp]
|
|
public void RaiseThreadPoolMinimum()
|
|
{
|
|
ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
|
|
ThreadPool.SetMinThreads(
|
|
Math.Max(workerThreads, kMinThreads),
|
|
Math.Max(completionPortThreads, kMinThreads));
|
|
}
|
|
}
|