当并行运行测试时,为WebApi OWIN自主机获取免费端口
本文关键字:主机 获取 免费 OWIN WebApi 运行测试 并行 | 更新日期: 2023-09-27 18:24:27
我使用OWIN to Self-Host Web API,同时使用NCrunch在并行中运行测试,并在BeforeEach中启动它,在AfterEach方法中停止。
在每次测试之前,我都试图获得可用的自由端口,但通常85次测试中有5-10次失败,以下例外:
System.Net.HttpListenerException : Failed to listen on prefix
'http://localhost:3369/' because it conflicts with an existing registration on the machine.
因此,有时我似乎没有可用的端口。我试图使用Interlocked类来在多个线程之间共享最后使用的端口,但没有帮助。
这是我的测试基类:
public class BaseSteps
{
private const int PortRangeStart = 3368;
private const int PortRangeEnd = 8968;
private static long _portNumber = PortRangeStart;
private IDisposable _webServer;
//.....
[BeforeScenario]
public void Before()
{
Url = GetFullUrl();
_webServer = WebApp.Start<TestStartup>(Url);
}
[AfterScenario]
public void After()
{
_webServer.Dispose();
}
private static string GetFullUrl()
{
var ipAddress = IPAddress.Loopback;
var portAvailable = GetAvailablePort(PortRangeStart, PortRangeEnd, ipAddress);
return String.Format("http://{0}:{1}/", "localhost", portAvailable);
}
private static int GetAvailablePort(int rangeStart, int rangeEnd, IPAddress ip, bool includeIdlePorts = false)
{
IPGlobalProperties ipProps = IPGlobalProperties.GetIPGlobalProperties();
// if the ip we want a port on is an 'any' or loopback port we need to exclude all ports that are active on any IP
Func<IPAddress, bool> isIpAnyOrLoopBack = i => IPAddress.Any.Equals(i) ||
IPAddress.IPv6Any.Equals(i) ||
IPAddress.Loopback.Equals(i) ||
IPAddress.IPv6Loopback.
Equals(i);
// get all active ports on specified IP.
List<ushort> excludedPorts = new List<ushort>();
// if a port is open on an 'any' or 'loopback' interface then include it in the excludedPorts
excludedPorts.AddRange(from n in ipProps.GetActiveTcpConnections()
where
n.LocalEndPoint.Port >= rangeStart &&
n.LocalEndPoint.Port <= rangeEnd && (
isIpAnyOrLoopBack(ip) || n.LocalEndPoint.Address.Equals(ip) ||
isIpAnyOrLoopBack(n.LocalEndPoint.Address)) &&
(!includeIdlePorts || n.State != TcpState.TimeWait)
select (ushort)n.LocalEndPoint.Port);
excludedPorts.AddRange(from n in ipProps.GetActiveTcpListeners()
where n.Port >= rangeStart && n.Port <= rangeEnd && (
isIpAnyOrLoopBack(ip) || n.Address.Equals(ip) || isIpAnyOrLoopBack(n.Address))
select (ushort)n.Port);
excludedPorts.AddRange(from n in ipProps.GetActiveUdpListeners()
where n.Port >= rangeStart && n.Port <= rangeEnd && (
isIpAnyOrLoopBack(ip) || n.Address.Equals(ip) || isIpAnyOrLoopBack(n.Address))
select (ushort)n.Port);
excludedPorts.Sort();
for (int port = rangeStart; port <= rangeEnd; port++)
{
if (!excludedPorts.Contains((ushort)port) && Interlocked.Read(ref _portNumber) < port)
{
Interlocked.Increment(ref _portNumber);
return port;
}
}
return 0;
}
}
有人知道如何确保我总是获得可用端口吗?
代码中的问题就在这里:
if (!excludedPorts.Contains((ushort)port) && Interlocked.Read(ref _portNumber) < port)
{
Interlocked.Increment(ref _portNumber);
return port;
}
首先,您可以在每次测试开始时计算一次excludedPorts
,并将它们存储在某个静态字段中。
其次,这个问题是由定义端口是否可用的错误逻辑引起的:在Interlocked.Read
和Interlocked.Increment
之间,其他线程可以进行相同的检查并返回相同的端口!例如:
- 线程A:检查
3369
:它不在excludedPorts
中,并且_portNumber
等于3368,所以检查通过。但是停下来,我会想一想 - 线程B:检查
3369
:它不在excludedPorts
中,并且_portNumber
等于3368,所以检查也通过了!哇,我太激动了,让我们Increment
吧,然后返回3369
- 线索A:好吧,那我们在哪里?哦,是的,
Increment
,并返回3369
典型的比赛条件。你可以用两种方法来解决它:
使用
Interlocked
类中的CAS-operationCompareExchange
(您可以删除port
变量,类似这样的东西(请自行测试此代码):var portNumber = _portNumber; if (excludedPorts.Contains((ushort)portNumber)) { // if port already taken continue; } if (Interlocked.CompareExchange(ref _portNumber, portNumber + 1, portNumber) != portNumber)) { // if exchange operation failed, other thread passed through continue; } // only one thread can succeed return portNumber;
使用端口的静态
ConcurrentDictionary
,并向其添加新端口,类似于以下内容(您可以选择另一个集合):// static field in your class // value item isn't useful static ConcurrentDictionary<int, bool>() ports = new ConcurrentDictionary<int, bool>(); foreach (var p in excludedPorts) // you may check here is the adding the port succeed ports.TryAdd(p, true); var portNumber = _portNumber; if (!ports.TryAdd(portNumber, true)) { continue; } return portNumber;