tgstation-server 6.12.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.
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 on...
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, 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.