/* ========================================================================
* 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.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua.Configuration;
using Opc.Ua.Tests;
using Quickstarts.ReferenceServer;
namespace Opc.Ua.Server.TestFramework
{
///
/// Server fixture for testing.
///
/// A server class T used for testing.
// CA1001: test fixture; lifecycle is handled via async StopAsync
// (Application.DisposeAsync at line 445), not the IDisposable pattern.
#pragma warning disable CA1001
public class ServerFixture
#pragma warning restore CA1001
where T : ServerBase
{
public IApplicationInstance Application { get; private set; } = null!;
public ApplicationConfiguration Config { get; private set; } = null!;
public T Server { get; private set; } = null!;
public bool AutoAccept { get; set; }
public bool OperationLimits { get; set; }
// Keep room for the default 100 Sessions, one replacement channel and two protected startup floors.
public int MaxChannelCount { get; set; } = 103;
public int ReverseConnectTimeout { get; set; }
public bool AllNodeManagers { get; set; }
///
/// Whether the fixture's server refuses to mint a NodeId it already
/// gave a different browse path. On by default, so a test run
/// exercises the check whatever configuration the stack was built in.
///
public bool DetectNodeIdCollisions { get; set; } = true;
public int TraceMasks { get; set; } =
Utils.TraceMasks.Error |
Utils.TraceMasks.StackTrace |
Utils.TraceMasks.Security |
Utils.TraceMasks.Information;
public bool SecurityNone { get; set; }
public string UriScheme { get; set; } = Utils.UriSchemeOpcTcp;
///
/// When true (default), HTTPS / WSS endpoints require client
/// TLS authentication and the listener emits the configured
/// secure security policy. When false, mutual TLS is off and
/// the listener emits a
/// endpoint - required for the JSON sub-protocols
/// (HTTPS-JSON, WSS-JSON) which do not use UA Secure Conversation.
///
public bool HttpsMutualTls { get; set; } = true;
public int Port { get; private set; }
public bool UseTracing { get; }
public bool DurableSubscriptionsEnabled { get; set; }
public bool UseSamplingGroupsInReferenceNodeManager { get; set; }
public bool ProvisioningMode { get; set; }
public ActivityListener ActivityListener { get; private set; } = null!;
///
/// Optional
/// assigned to the server immediately after construction (before
/// StartAsync). Used by integration tests that swap a
/// listener / channel factory for a binding under test - e.g. the
/// Kestrel-TCP listener fixture - without touching the
/// process-wide static state.
///
public Bindings.ITransportBindingRegistry TransportBindingRegistry { get; set; } = null!;
public ServerFixture(
Func factory,
bool useTracing,
bool disableActivityLogging)
: this(factory)
{
UseTracing = useTracing;
if (UseTracing)
{
StartActivityListenerInternal(disableActivityLogging);
}
}
public ServerFixture(Func factory)
{
m_factory = factory;
m_telemetry = NUnitTelemetryContext.Create(true);
m_logger = m_telemetry.CreateLogger>();
}
public async Task LoadConfigurationAsync(string? pkiRoot = null)
{
Application = new ApplicationInstance(m_telemetry)
{
ApplicationName = typeof(T).Name,
ApplicationType = ApplicationType.Server
};
// create the application configuration. Use temp path for cert stores.
pkiRoot ??= Path.GetTempPath() + Path.GetRandomFileName();
string endpointUrl = $"{UriScheme}://localhost:0/" + typeof(T).Name;
IApplicationConfigurationBuilderServerSelected serverConfig = Application
.Build(
"urn:localhost:UA:" + typeof(T).Name,
"uri:opcfoundation.org:" + typeof(T).Name)
.SetMaxByteStringLength(4 * 1024 * 1024)
.SetMaxArrayLength(1024 * 1024)
.SetMaxMessageSize(16 * 1024 * 1024)
.SetChannelLifetime(30000)
.AsServer([endpointUrl]);
if (SecurityNone)
{
serverConfig.AddUnsecurePolicyNone();
}
if (Utils.IsUriHttpsScheme(endpointUrl) || Utils.IsUriWssScheme(endpointUrl))
{
serverConfig.AddPolicy(
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.Basic256Sha256);
}
else if (endpointUrl.StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
{
// add deprecated policies for opc.tcp tests
serverConfig
.AddSignPolicies()
.AddSignAndEncryptPolicies()
.AddEccSignPolicies()
.AddEccSignAndEncryptPolicies();
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.Basic128Rsa15);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.Basic128Rsa15);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.Basic256);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.Basic256);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.RSA_DH_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.RSA_DH_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.RSA_DH_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.RSA_DH_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_nistP256_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_nistP256_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_nistP256_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_nistP256_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_nistP384_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_nistP384_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_nistP384_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_nistP384_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_brainpoolP384r1_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_brainpoolP384r1_AesGcm);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.Sign,
SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly);
AddPolicyIfSupported(
serverConfig,
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly);
}
if (OperationLimits)
{
serverConfig.SetOperationLimits(
new OperationLimits
{
MaxNodesPerBrowse = 2500,
MaxNodesPerRead = 1000,
MaxNodesPerWrite = 1000,
MaxNodesPerMethodCall = 1000,
MaxMonitoredItemsPerCall = 1000,
MaxNodesPerHistoryReadData = 1000,
MaxNodesPerHistoryReadEvents = 1000,
MaxNodesPerHistoryUpdateData = 1000,
MaxNodesPerHistoryUpdateEvents = 1000,
MaxNodesPerNodeManagement = 1000,
MaxNodesPerRegisterNodes = 1000,
MaxNodesPerTranslateBrowsePathsToNodeIds = 1000
});
}
serverConfig
.SetMaxChannelCount(MaxChannelCount)
.SetMaxMessageQueueSize(20)
.SetDiagnosticsEnabled(true)
.SetAuditingEnabled(true)
.SetHttpsMutualTls(HttpsMutualTls)
// Tests always close the session explicitly before stopping the server,
// so no real client needs the shutdown-delay grace period. Setting to 0
// eliminates the Thread.Sleep(1000)×ShutdownDelay blocking call in
// StandardServer.ShutDownDelay(), which otherwise races with the
// ServerFixture teardown watchdog (both default to 5 s) and causes a
// post-test process hang on macOS where Thread.Sleep drifts slightly
// above 1 s per iteration.
.SetShutdownDelay(0);
if (ReverseConnectTimeout != 0)
{
serverConfig.SetReverseConnect(
new ReverseConnectServerConfiguration
{
ConnectInterval = ReverseConnectTimeout / 4,
ConnectTimeout = ReverseConnectTimeout,
RejectTimeout = ReverseConnectTimeout / 4
});
}
ArrayOf applicationCerts =
ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates(
"CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost",
CertificateStoreType.Directory,
pkiRoot);
if (DurableSubscriptionsEnabled)
{
serverConfig
.SetDurableSubscriptionsEnabled(true)
.SetMaxDurableEventQueueSize(10000)
.SetMaxDurableNotificationQueueSize(1000)
.SetMaxDurableSubscriptionLifetime(3600);
}
Config = await serverConfig
.AddSecurityConfiguration(applicationCerts, pkiRoot)
.SetAutoAcceptUntrustedCertificates(AutoAccept)
.CreateAsync()
.ConfigureAwait(false);
}
///
/// Start server fixture on random or fixed port.
///
public Task StartAsync(int port = 0)
{
return StartAsync(null!, port);
}
///
/// Start server fixture on random or fixed port with dedicated PKI.
///
public async Task StartAsync(string pkiRoot, int port = 0)
{
int testPort = port;
int serverStartRetries = 1;
if (Application == null)
{
await LoadConfigurationAsync(pkiRoot).ConfigureAwait(false);
}
if (port <= 0)
{
testPort = ServerFixtureUtils.GetNextFreeIPPort();
serverStartRetries = 25;
}
bool retryStartServer;
do
{
retryStartServer = false;
try
{
await InternalStartServerAsync(testPort).ConfigureAwait(false);
}
catch (Exception ex)
when (serverStartRetries > 0 && ServerFixtureUtils.IsPortUnavailable(ex))
{
serverStartRetries--;
testPort = UnsecureRandom.Shared.Next(
ServerFixtureUtils.MinTestPort,
ServerFixtureUtils.MaxTestPort);
retryStartServer = true;
}
catch (Exception ex)
when (serverStartRetries > 0 && IsAddressAlreadyInUse(ex))
{
// The TCP listener reports a taken port as BadNoCommunication,
// which the clause above retries. The HTTPS listener does not: it
// lets Kestrel's IOException out verbatim, so without this the
// retry designed for exactly this case never runs and the whole
// fixture fails on a port race it was built to survive.
serverStartRetries--;
testPort = UnsecureRandom.Shared.Next(
ServerFixtureUtils.MinTestPort,
ServerFixtureUtils.MaxTestPort);
retryStartServer = true;
}
await Task.Delay(UnsecureRandom.Shared.Next(100, 1000)).ConfigureAwait(false);
} while (retryStartServer);
return Server;
}
///
/// Create the configuration and start the server.
///
///
private async Task InternalStartServerAsync(int port)
{
Config.ServerConfiguration!.BaseAddresses
= [$"{UriScheme}://localhost:{port}/{typeof(T).Name}"];
// check the application certificate.
bool haveAppCertificate = await Application
.CheckApplicationInstanceCertificatesAsync(true, CertificateFactory.DefaultLifeTime)
.ConfigureAwait(false);
if (!haveAppCertificate)
{
throw new InvalidOperationException("Application instance certificate invalid!");
}
// start the server.
T server = m_factory(m_telemetry);
server.TransportBindings = TransportBindingRegistry
?? TestTransportBindings.WithAllSchemes();
if (server is StandardServer nodeIdCollisionServer)
{
// On for every test server whatever configuration the stack
// was built in. Off is the release default, so leaving it
// alone would mean a release test run never exercises the
// check and a collision would surface as a silently replaced
// node instead of a failing test.
nodeIdCollisionServer.DetectNodeIdCollisions = DetectNodeIdCollisions;
}
if (AllNodeManagers && server is StandardServer standardServer)
{
Quickstarts.Servers.Utils.AddDefaultNodeManagers(standardServer);
}
if (UseSamplingGroupsInReferenceNodeManager &&
server is ReferenceServer referenceServer)
{
Quickstarts.Servers.Utils.UseSamplingGroupsInReferenceNodeManager(referenceServer);
}
if (ProvisioningMode &&
server is ReferenceServer provisioningReferenceServer)
{
Quickstarts.Servers.Utils.EnableProvisioningMode(provisioningReferenceServer);
}
m_startupCts = new CancellationTokenSource();
await Application.StartAsync(server, m_startupCts.Token).ConfigureAwait(false);
Server = server;
Port = port;
}
///
/// Configures Activity Listener and registers with Activity Source.
///
public void StartActivityListenerInternal(bool disableActivityLogging = false)
{
// Source construction invokes ShouldListenTo synchronously, so resolve before registration.
ActivitySource activitySource = m_telemetry.GetActivitySource();
string expectedName = activitySource.Name;
if (disableActivityLogging)
{
// Create an instance of ActivityListener without logging
ActivityListener = new ActivityListener
{
ShouldListenTo = source => source.Name == expectedName,
Sample = (ref _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = _ => { },
ActivityStopped = _ => { }
};
}
else
{
// Create an instance of ActivityListener and configure its properties with logging
ActivityListener = new ActivityListener
{
ShouldListenTo = source => source.Name == expectedName,
Sample = (ref _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = activity =>
{
if (m_logger.IsEnabled(LogLevel.Information))
{
m_logger.LogInformation(
"Server Started: {OperationName,-15} - TraceId: {TraceId,-32} SpanId: {SpanId,-16} ParentId: {ParentId,-32}",
activity.OperationName,
activity.TraceId,
activity.SpanId,
activity.ParentId);
}
},
ActivityStopped = activity =>
{
if (m_logger.IsEnabled(LogLevel.Information))
{
m_logger.LogInformation(
"Server Stopped: {OperationName,-15} - TraceId: {TraceId,-32} SpanId: {SpanId,-16} ParentId: {ParentId,-32} Duration: {Duration}",
activity.OperationName,
activity.TraceId,
activity.SpanId,
activity.ParentId,
activity.Duration);
}
}
};
}
ActivitySource.AddActivityListener(ActivityListener);
}
///
/// Stop the server.
///
public async Task StopAsync()
{
// Cancel any in-progress startup (e.g., address space creation)
m_startupCts?.Dispose();
m_startupCts = null!;
// Watchdog around server / application teardown. Without it, a
// stuck Server.StopAsync() or Application.DisposeAsync() blocks
// the per-fixture OneTimeTearDown indefinitely, and the entire
// dotnet test host hangs until the --blame-hang-timeout fires
// (observed in macOS net10.0 CI runs of Opc.Ua.History.Tests:
// all 466+ tests pass, then a 10-minute teardown hang followed
// by a hang dump and exit code 1). Mirrors the bounded-thread
// pattern in LeakDetectionHelpers.TryRunFinalizerSweep so a
// process-killing hang becomes an observable per-fixture
// warning while subsequent fixtures' teardowns and the dotnet
// host shutdown can still complete.
//
// The per-call timeout is kept tight (5s) because each test
// project has up to 27 fixtures; a 30s budget per call would
// exceed the 10-minute dotnet test hang timeout once even a
// handful of fixtures hit the watchdog in series. A graceful
// shutdown in the healthy case completes in well under 5s.
// The same timeout is propagated as a CancellationToken so the
// server can short-circuit instead of being abandoned (the
// server StopAsync overload threads the token through
// RegisterWithDiscoveryServerAsync, the shutdown semaphore,
// SubscriptionManager.ShutdownAsync and
// NodeManager.ShutdownAsync).
//
// GLOBAL FAST-PATH: once any fixture's teardown exceeds the
// watchdog in this test process, set a static "give up
// teardown" flag and short-circuit subsequent fixtures'
// teardowns to a no-op. This converts the worst case from
// (27 fixtures x N watchdog operations x 5s) into a single
// ~10s teardown stall: only the first hung fixture pays the
// watchdog cost; the rest skip directly to the next test
// host shutdown step. We accept the resource leak as the
// test process is about to exit.
if (s_skipRemainingTeardowns)
{
m_logger.LogWarning(
"ServerFixture.StopAsync: a prior fixture teardown timed out; " +
"skipping the remaining server / application disposal in this process to " +
"avoid pinning the dotnet test host past --blame-hang-timeout. " +
"References will be released to the runtime for finalization.");
DisposeCertificateManagers();
Server = null!;
Application = null!;
Config = null!;
ActivityListener?.Dispose();
ActivityListener = null!;
return;
}
if (Server != null)
{
using var serverStopCts = new CancellationTokenSource(s_teardownTimeout);
await RunWithTeardownWatchdogAsync(
() => Server.StopAsync(serverStopCts.Token).AsTask(),
nameof(Server) + "." + nameof(Server.StopAsync)).ConfigureAwait(false);
RunSyncWithTeardownWatchdog(
() => Server.Dispose(),
nameof(Server) + "." + nameof(Server.Dispose));
DisposeCertificateManagers();
Server = null!;
}
if (Application != null)
{
try
{
await RunWithTeardownWatchdogAsync(
() => Application.DisposeAsync().AsTask(),
nameof(Application) + "." + nameof(Application.DisposeAsync)).ConfigureAwait(false);
}
finally
{
DisposeCertificateManagers();
Application = null!;
}
}
Config = null!;
ActivityListener?.Dispose();
ActivityListener = null!;
await Task.Delay(100).ConfigureAwait(false);
}
private void DisposeCertificateManagers()
{
var applicationManager = Application?.ApplicationConfiguration?.CertificateManager as IDisposable;
applicationManager?.Dispose();
var configManager = Config?.CertificateManager as IDisposable;
if (configManager != null &&
!ReferenceEquals(configManager, applicationManager))
{
configManager.Dispose();
}
if (Server?.CertificateManager is IDisposable serverManager &&
!ReferenceEquals(serverManager, applicationManager) &&
!ReferenceEquals(serverManager, configManager))
{
serverManager.Dispose();
}
}
///
/// Gets whether a start failure was a taken port, anywhere in its chain.
///
///
/// The listener that failed decides the shape: TCP raises a
/// , while Kestrel wraps a
/// in an AddressInUseException inside an
/// . Only the socket error at the bottom is common to
/// both, so that is what this looks for.
///
private static bool IsAddressAlreadyInUse(Exception exception)
{
for (Exception current = exception;
current is not null;
current = current.InnerException!)
{
if (current is SocketException socket &&
socket.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
return true;
}
if (current is AggregateException aggregate)
{
foreach (Exception inner in aggregate.InnerExceptions)
{
if (IsAddressAlreadyInUse(inner))
{
return true;
}
}
}
}
return false;
}
private static readonly TimeSpan s_teardownTimeout = TimeSpan.FromSeconds(5);
///
/// Set by the first or
/// invocation that
/// exceeds the watchdog. Subsequent fixtures'
/// calls short-circuit to a no-op so we do not stack N x watchdog
/// timeouts during a known-bad teardown chain.
///
private static volatile bool s_skipRemainingTeardowns;
///
/// Runs bounded by
/// . If the task does not complete
/// in time, logs a warning, sets
/// so subsequent fixtures short-circuit, and returns; the
/// underlying task is abandoned (it is a background continuation
/// in the runtime thread pool, so it does not block process
/// exit). Cross-TFM compatible: avoids
/// Task.WaitAsync(TimeSpan) which is .NET 6+; the test
/// framework targets net48 and netstandard2.0 among others.
///
private async Task RunWithTeardownWatchdogAsync(
Func taskFactory,
string operationName)
{
Task work;
try
{
work = taskFactory();
}
catch (Exception ex)
{
m_logger.LogWarning(
ex,
"ServerFixture.StopAsync: {Operation} threw synchronously.",
operationName);
return;
}
Task winner = await Task
.WhenAny(work, Task.Delay(s_teardownTimeout))
.ConfigureAwait(false);
if (winner != work)
{
s_skipRemainingTeardowns = true;
m_logger.LogWarning(
"ServerFixture.StopAsync: {Operation} exceeded the {TimeoutSeconds}s teardown watchdog. " +
"Continuing with the rest of the fixture teardown; subsequent fixtures will short-circuit.",
operationName,
s_teardownTimeout.TotalSeconds);
return;
}
try
{
await work.ConfigureAwait(false);
}
catch (Exception ex)
{
m_logger.LogWarning(
ex,
"ServerFixture.StopAsync: {Operation} faulted.",
operationName);
}
}
///
/// Runs a synchronous on a background
/// thread bounded by . Used for
/// synchronous Dispose() calls that can block on socket
/// shutdown or thread joins; the async watchdog above only
/// bounds -returning calls and cannot interrupt
/// a sync method that ignores cancellation.
///
private void RunSyncWithTeardownWatchdog(Action action, string operationName)
{
using var done = new ManualResetEventSlim(false);
Exception? captured = null;
var worker = new Thread(() =>
{
try
{
action();
}
catch (Exception ex)
{
captured = ex;
}
finally
{
done.Set();
}
})
{
IsBackground = true,
Name = "ServerFixture.SyncTeardown." + operationName
};
worker.Start();
if (!done.Wait(s_teardownTimeout))
{
s_skipRemainingTeardowns = true;
m_logger.LogWarning(
"ServerFixture.StopAsync: {Operation} exceeded the {TimeoutSeconds}s teardown watchdog (sync). " +
"Continuing with the rest of the fixture teardown; subsequent fixtures will short-circuit.",
operationName,
s_teardownTimeout.TotalSeconds);
return;
}
if (captured != null)
{
m_logger.LogWarning(
captured,
"ServerFixture.StopAsync: {Operation} threw.",
operationName);
}
}
private static void AddPolicyIfSupported(
IApplicationConfigurationBuilderServerSelected serverConfig,
MessageSecurityMode securityMode,
string securityPolicyUri)
{
if (SecurityPolicies.Default.GetInfo(securityPolicyUri) != null)
{
serverConfig.AddPolicy(securityMode, securityPolicyUri);
}
}
private CancellationTokenSource m_startupCts = null!;
private readonly Func m_factory;
private readonly ITelemetryContext m_telemetry;
private readonly ILogger m_logger;
}
}