tgstation-server 6.8.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 ILogger<PortAllocator> logger;
41
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 swarmConfiguration = swarmConfigurationOptions?.Value ?? 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 .AsQueryable()
103 .Where(x => x.Instance!.SwarmIdentifer == swarmConfiguration.Identifier)
104 .Select(x => new
105 {
106 Port = x.Port!.Value,
107 x.InstanceId,
108 })
109 .ToListAsync(cancellationToken);
110
111 var dmPorts = await databaseContext
113 .AsQueryable()
114 .Where(x => x.Instance!.SwarmIdentifer == swarmConfiguration.Identifier)
115 .Select(x => new
116 {
117 ApiValidationPort = x.ApiValidationPort!.Value,
118 x.InstanceId,
119 })
120 .ToListAsync(cancellationToken);
121
122 var exceptions = new List<Exception>();
123 ushort port = 0;
124 try
125 {
126 for (port = basePort; port < ushort.MaxValue; ++port)
127 {
128 if (checkOne && port != basePort)
129 break;
130
132 {
133 logger.LogWarning("Cannot allocate port {port} as it is the TGS API port!", port);
134 continue;
135 }
136
137 var reservedGamePortData = ddPorts.Where(data => data.Port == port).ToList();
138 if (reservedGamePortData.Count > 0)
139 {
140 logger.LogWarning(
141 "Cannot allocate port {port} as it in use by the game server of instance(s): {instanceId}!",
142 port,
143 String.Join(
144 ", ",
145 reservedGamePortData.Select(data => data.InstanceId)));
146 continue;
147 }
148
149 var reservedApiValidationPortData = dmPorts.Where(data => data.ApiValidationPort == port).ToList();
150 if (reservedApiValidationPortData.Count > 0)
151 {
152 logger.LogWarning(
153 "Cannot allocate port {port} as it in use by the API validation server of instance(s): {instanceId}!",
154 port,
155 String.Join(
156 ", ",
157 reservedApiValidationPortData.Select(data => data.InstanceId)));
158 continue;
159 }
160
161 try
162 {
163 SocketExtensions.BindTest(platformIdentifier, port, false, true);
164 SocketExtensions.BindTest(platformIdentifier, port, false, false);
165 }
166 catch (Exception ex)
167 {
168 exceptions.Add(ex);
169 continue;
170 }
171
172 logger.LogInformation("Allocated port {port}", port);
173 return port;
174 }
175
176 logger.LogWarning("Unable to allocate port >= {basePort}!", basePort);
177 return null;
178 }
179 finally
180 {
181 if (port != basePort)
182 {
183 logger.LogDebug(
184 exceptions.Count == 1
185 ? exceptions.First()
186 : new AggregateException(exceptions),
187 "Failed to allocate ports {basePort}-{lastCheckedPort}!",
188 basePort,
189 port - 1);
190 }
191 }
192 }
193 }
194}
string? Identifier
The server's identifier.
Definition: SwarmServer.cs:26
Configuration for the server swarm system.
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 o...
readonly ILogger< PortAllocator > logger
The ILogger for the PortAllocator.
readonly SwarmConfiguration swarmConfiguration
The 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)
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.