tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
PortAllocator.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.EntityFrameworkCore;
8using Microsoft.Extensions.Logging;
9using Microsoft.Extensions.Options;
10
16
18{
21 {
26
31
36
40 readonly IOptions<SwarmConfiguration> swarmConfigurationOptions;
41
45 readonly ILogger<PortAllocator> logger;
46
50 readonly SemaphoreSlim allocatorLock;
51
64 IOptions<SwarmConfiguration> swarmConfigurationOptions,
65 ILogger<PortAllocator> logger)
66 {
67 this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
68 this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
69 this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
70 this.swarmConfigurationOptions = swarmConfigurationOptions ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
71 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
72
73 allocatorLock = new SemaphoreSlim(1);
74 }
75
77 public void Dispose() => allocatorLock.Dispose();
78
80 public async ValueTask<ushort?> GetAvailablePort(ushort basePort, bool checkOne, CancellationToken cancellationToken)
81 {
82 ushort? result = null;
83 using (await SemaphoreSlimContext.Lock(allocatorLock, cancellationToken))
85 async databaseContext => result = await GetAvailablePort(databaseContext, basePort, checkOne, cancellationToken));
86 return result;
87 }
88
97 async ValueTask<ushort?> GetAvailablePort(IDatabaseContext databaseContext, ushort basePort, bool checkOne, CancellationToken cancellationToken)
98 {
99 logger.LogTrace("Port allocation >= {basePort} requested...", basePort);
100 var ddPorts = await databaseContext
102 .Where(x => x.Instance!.SwarmIdentifer == swarmConfigurationOptions.Value.Identifier)
103 .Select(x => new
104 {
105 Port = x.Port!.Value,
106 x.InstanceId,
107 })
108 .ToListAsync(cancellationToken);
109
110 var dmPorts = await databaseContext
112 .Where(x => x.Instance!.SwarmIdentifer == swarmConfigurationOptions.Value.Identifier)
113 .Select(x => new
114 {
115 ApiValidationPort = x.ApiValidationPort!.Value,
116 x.InstanceId,
117 })
118 .ToListAsync(cancellationToken);
119
120 var exceptions = new List<Exception>();
121 ushort port = 0;
122 try
123 {
124 for (port = basePort; port < ushort.MaxValue; ++port)
125 {
126 if (checkOne && port != basePort)
127 break;
128
130 {
131 logger.LogWarning("Cannot allocate port {port} as it is the TGS API port!", port);
132 continue;
133 }
134
135 var reservedGamePortData = ddPorts.Where(data => data.Port == port).ToList();
136 if (reservedGamePortData.Count > 0)
137 {
138 logger.LogWarning(
139 "Cannot allocate port {port} as it in use by the game server of instance(s): {instanceId}!",
140 port,
141 String.Join(
142 ", ",
143 reservedGamePortData.Select(data => data.InstanceId)));
144 continue;
145 }
146
147 var reservedApiValidationPortData = dmPorts.Where(data => data.ApiValidationPort == port).ToList();
148 if (reservedApiValidationPortData.Count > 0)
149 {
150 logger.LogWarning(
151 "Cannot allocate port {port} as it in use by the API validation server of instance(s): {instanceId}!",
152 port,
153 String.Join(
154 ", ",
155 reservedApiValidationPortData.Select(data => data.InstanceId)));
156 continue;
157 }
158
159 try
160 {
161 SocketExtensions.BindTest(platformIdentifier, port, false, true);
162 SocketExtensions.BindTest(platformIdentifier, port, false, false);
163 }
164 catch (Exception ex)
165 {
166 exceptions.Add(ex);
167 continue;
168 }
169
170 logger.LogInformation("Allocated port {port}", port);
171 return port;
172 }
173
174 logger.LogWarning("Unable to allocate port >= {basePort}!", basePort);
175 return null;
176 }
177 finally
178 {
179 if (port != basePort)
180 {
181 logger.LogDebug(
182 exceptions.Count == 1
183 ? exceptions.First()
184 : new AggregateException(exceptions),
185 "Failed to allocate ports {basePort}-{lastCheckedPort}!",
186 basePort,
187 port - 1);
188 }
189 }
190 }
191 }
192}
Extension methods for the Socket class.
static void BindTest(IPlatformIdentifier platformIdentifier, ushort port, bool includeIPv6, bool udp)
Attempt to exclusively bind to a given port .
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the PortAllocator.
async ValueTask< ushort?> GetAvailablePort(ushort basePort, bool checkOne, CancellationToken cancellationToken)
Gets a port not currently in use by TGS.A ValueTask<TResult> resulting in the first available port on...
readonly ILogger< PortAllocator > logger
The ILogger for the PortAllocator.
readonly IOptions< SwarmConfiguration > swarmConfigurationOptions
The IOptions<TOptions> of SwarmConfiguration for the PortAllocator.
async ValueTask< ushort?> GetAvailablePort(IDatabaseContext databaseContext, ushort basePort, bool checkOne, CancellationToken cancellationToken)
Gets a port not currently in use by TGS.
readonly IServerPortProvider serverPortProvider
The IServerPortProvider for the PortAllocator.
PortAllocator(IServerPortProvider serverPortProvider, IDatabaseContextFactory databaseContextFactory, IPlatformIdentifier platformIdentifier, IOptions< SwarmConfiguration > swarmConfigurationOptions, ILogger< PortAllocator > logger)
Initializes a new instance of the PortAllocator class.
readonly SemaphoreSlim allocatorLock
The SemaphoreSlim used to serialized port requisition requests.
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContext for the PortAllocator.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Provides access to the server's HttpApiPort.
ushort HttpApiPort
The port the server listens on.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
ValueTask UseContext(Func< IDatabaseContext, ValueTask > operation)
Run an operation in the scope of an IDatabaseContext.
IDatabaseCollection< DreamDaemonSettings > DreamDaemonSettings
The Models.DreamDaemonSettings in the IDatabaseContext.
IDatabaseCollection< DreamMakerSettings > DreamMakerSettings
The Models.DreamMakerSettings in the IDatabaseContext.
For identifying the current platform.
Gets unassigned ports for use by TGS.