Download rework (#22)
* rework server to send download ready back via signalr * adjust queue handling for removal * adjust api to main Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
@@ -37,7 +37,8 @@
|
|||||||
"MainServerGrpcAddress": "http://mare-server:6005",
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
"DownloadTimeoutSeconds": 30,
|
"DownloadTimeoutSeconds": 30,
|
||||||
"DownloadQueueSize": 50,
|
"DownloadQueueSize": 50,
|
||||||
"DownloadQueueReleaseSeconds": 15
|
"DownloadQueueReleaseSeconds": 15,
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
"MainServerGrpcAddress": "http://mare-server:6005",
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
"DownloadTimeoutSeconds": 30,
|
"DownloadTimeoutSeconds": 30,
|
||||||
"DownloadQueueSize": 50,
|
"DownloadQueueSize": 50,
|
||||||
"DownloadQueueReleaseSeconds": 15
|
"DownloadQueueReleaseSeconds": 15,
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
"UnusedFileRetentionPeriodInDays": 14,
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
"CacheDirectory": "/marecache/",
|
"CacheDirectory": "/marecache/",
|
||||||
"RemoteCacheSourceUri": "",
|
"RemoteCacheSourceUri": "",
|
||||||
"MainServerGrpcAddress": "http://mare-server:6005"
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
"UnusedFileRetentionPeriodInDays": 14,
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
"CacheDirectory": "/marecache/",
|
"CacheDirectory": "/marecache/",
|
||||||
"RemoteCacheSourceUri": "",
|
"RemoteCacheSourceUri": "",
|
||||||
"MainServerGrpcAddress": "http://mare-server:6005"
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
|
|||||||
@@ -63,5 +63,10 @@ namespace MareSynchronosServer.Hubs
|
|||||||
{
|
{
|
||||||
throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Client_DownloadReady(Guid requestId)
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,15 +83,6 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Authenticated")]
|
|
||||||
public async Task<ConnectionDto> Heartbeat(string characterIdentification)
|
|
||||||
{
|
|
||||||
return new ConnectionDto()
|
|
||||||
{
|
|
||||||
ServerVersion = IMareHub.ApiVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize(Policy = "Authenticated")]
|
[Authorize(Policy = "Authenticated")]
|
||||||
public async Task<bool> CheckClientHealth()
|
public async Task<bool> CheckClientHealth()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using MareSynchronosShared.Protos;
|
|||||||
using Grpc.Net.Client.Configuration;
|
using Grpc.Net.Client.Configuration;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
using MareSynchronosServer.Utils;
|
|
||||||
using MareSynchronosServer.RequirementHandlers;
|
using MareSynchronosServer.RequirementHandlers;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using MareSynchronosShared.Services;
|
using MareSynchronosShared.Services;
|
||||||
@@ -80,7 +79,6 @@ public class Startup
|
|||||||
|
|
||||||
services.AddSingleton<ServerTokenGenerator>();
|
services.AddSingleton<ServerTokenGenerator>();
|
||||||
services.AddSingleton<SystemInfoService>();
|
services.AddSingleton<SystemInfoService>();
|
||||||
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
|
||||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||||
// configure services based on main server status
|
// configure services based on main server status
|
||||||
ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer);
|
ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer);
|
||||||
@@ -94,6 +92,8 @@ public class Startup
|
|||||||
|
|
||||||
private static void ConfigureSignalR(IServiceCollection services, IConfigurationSection mareConfig)
|
private static void ConfigureSignalR(IServiceCollection services, IConfigurationSection mareConfig)
|
||||||
{
|
{
|
||||||
|
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||||
|
|
||||||
var signalRServiceBuilder = services.AddSignalR(hubOptions =>
|
var signalRServiceBuilder = services.AddSignalR(hubOptions =>
|
||||||
{
|
{
|
||||||
hubOptions.MaximumReceiveMessageSize = long.MaxValue;
|
hubOptions.MaximumReceiveMessageSize = long.MaxValue;
|
||||||
@@ -106,13 +106,7 @@ public class Startup
|
|||||||
|
|
||||||
// configure redis for SignalR
|
// configure redis for SignalR
|
||||||
var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
|
var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
|
||||||
if (!string.IsNullOrEmpty(redisConnection))
|
signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options => { });
|
||||||
{
|
|
||||||
signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options =>
|
|
||||||
{
|
|
||||||
options.Configuration.ChannelPrefix = "MareSynchronos";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = ConfigurationOptions.Parse(redisConnection);
|
var options = ConfigurationOptions.Parse(redisConnection);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using MareSynchronosShared.Utils;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Utils;
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
public class IdBasedUserIdProvider : IUserIdProvider
|
public class IdBasedUserIdProvider : IUserIdProvider
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using MareSynchronosStaticFilesServer.Services;
|
using MareSynchronosStaticFilesServer.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer.Controllers;
|
namespace MareSynchronosStaticFilesServer.Controllers;
|
||||||
|
|
||||||
@@ -20,6 +19,23 @@ public class RequestController : ControllerBase
|
|||||||
_requestQueue = requestQueue;
|
_requestQueue = requestQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route(MareFiles.Request_Cancel)]
|
||||||
|
public async Task<IActionResult> CancelQueueRequest(Guid requestId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _parallelRequestSemaphore.WaitAsync(HttpContext.RequestAborted);
|
||||||
|
_requestQueue.RemoveFromQueue(requestId, MareUser);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { return BadRequest(); }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_parallelRequestSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route(MareFiles.Request_Enqueue)]
|
[Route(MareFiles.Request_Enqueue)]
|
||||||
public async Task<IActionResult> PreRequestFilesAsync([FromBody] List<string> files)
|
public async Task<IActionResult> PreRequestFilesAsync([FromBody] List<string> files)
|
||||||
@@ -51,34 +67,8 @@ public class RequestController : ControllerBase
|
|||||||
await _parallelRequestSemaphore.WaitAsync(HttpContext.RequestAborted);
|
await _parallelRequestSemaphore.WaitAsync(HttpContext.RequestAborted);
|
||||||
Guid g = Guid.NewGuid();
|
Guid g = Guid.NewGuid();
|
||||||
_cachedFileProvider.DownloadFileWhenRequired(file, Authorization);
|
_cachedFileProvider.DownloadFileWhenRequired(file, Authorization);
|
||||||
var queueStatus = await _requestQueue.EnqueueUser(new(g, MareUser, file));
|
await _requestQueue.EnqueueUser(new(g, MareUser, file));
|
||||||
return Ok(JsonSerializer.Serialize(new QueueRequestDto(g, queueStatus)));
|
return Ok(g);
|
||||||
}
|
|
||||||
catch (OperationCanceledException) { return BadRequest(); }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_parallelRequestSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route(MareFiles.Request_CheckQueue)]
|
|
||||||
public async Task<IActionResult> CheckQueueAsync(Guid requestId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _parallelRequestSemaphore.WaitAsync(HttpContext.RequestAborted);
|
|
||||||
if (_requestQueue.IsActiveProcessing(requestId, MareUser, out _))
|
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_requestQueue.StillEnqueued(requestId, MareUser))
|
|
||||||
{
|
|
||||||
return Conflict();
|
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest();
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { return BadRequest(); }
|
catch (OperationCanceledException) { return BadRequest(); }
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class ServerFilesController : ControllerBase
|
|||||||
|
|
||||||
[HttpGet(MareFiles.ServerFiles_Get + "/{fileId}")]
|
[HttpGet(MareFiles.ServerFiles_Get + "/{fileId}")]
|
||||||
[Authorize(Policy = "Internal")]
|
[Authorize(Policy = "Internal")]
|
||||||
public async Task<IActionResult> GetFile(string fileId)
|
public IActionResult GetFile(string fileId)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"GetFile:{MareUser}:{fileId}");
|
_logger.LogInformation($"GetFile:{MareUser}:{fileId}");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
// this is a very hacky way to attach this file server to the main mare hub signalr instance via redis
|
||||||
|
// signalr publishes the namespace and hubname into the redis backend so this needs to be equal to the original
|
||||||
|
// but I don't need to reimplement the hub completely as I only exclusively use it for internal connection calling
|
||||||
|
// from the queue service so I keep the namespace and name of the class the same so it can connect to the same channel
|
||||||
|
// if anyone finds a better way to do this let me know
|
||||||
|
|
||||||
|
#pragma warning disable IDE0130 // Namespace does not match folder structure
|
||||||
|
#pragma warning disable MA0048 // File name must match type name
|
||||||
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
public class MareHub : Hub
|
||||||
|
{
|
||||||
|
public override Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnDisconnectedAsync(Exception exception)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore IDE0130 // Namespace does not match folder structure
|
||||||
|
#pragma warning restore MA0048 // File name must match type name
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="7.0.0" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Services;
|
using MareSynchronosShared.Services;
|
||||||
using MareSynchronosStaticFilesServer.Utils;
|
using MareSynchronosStaticFilesServer.Utils;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
|
||||||
@@ -13,46 +14,45 @@ public class RequestQueueService : IHostedService
|
|||||||
private readonly ConcurrentQueue<UserRequest> _queue = new();
|
private readonly ConcurrentQueue<UserRequest> _queue = new();
|
||||||
private readonly MareMetrics _metrics;
|
private readonly MareMetrics _metrics;
|
||||||
private readonly ILogger<RequestQueueService> _logger;
|
private readonly ILogger<RequestQueueService> _logger;
|
||||||
|
private readonly IHubContext<MareSynchronosServer.Hubs.MareHub> _hubContext;
|
||||||
private readonly int _queueExpirationSeconds;
|
private readonly int _queueExpirationSeconds;
|
||||||
private SemaphoreSlim _queueSemaphore = new(1);
|
private readonly SemaphoreSlim _queueSemaphore = new(1);
|
||||||
private SemaphoreSlim _queueProcessingSemaphore = new(1);
|
private readonly SemaphoreSlim _queueProcessingSemaphore = new(1);
|
||||||
private System.Timers.Timer _queueTimer;
|
private System.Timers.Timer _queueTimer;
|
||||||
|
private readonly ConcurrentDictionary<Guid, string> _queueRemoval = new();
|
||||||
|
|
||||||
public RequestQueueService(MareMetrics metrics, IConfigurationService<StaticFilesServerConfiguration> configurationService, ILogger<RequestQueueService> logger)
|
public RequestQueueService(MareMetrics metrics, IConfigurationService<StaticFilesServerConfiguration> configurationService, ILogger<RequestQueueService> logger, IHubContext<MareSynchronosServer.Hubs.MareHub> hubContext)
|
||||||
{
|
{
|
||||||
_userQueueRequests = new UserQueueEntry[configurationService.GetValueOrDefault(nameof(StaticFilesServerConfiguration.DownloadQueueSize), 50)];
|
_userQueueRequests = new UserQueueEntry[configurationService.GetValueOrDefault(nameof(StaticFilesServerConfiguration.DownloadQueueSize), 50)];
|
||||||
_queueExpirationSeconds = configurationService.GetValueOrDefault(nameof(StaticFilesServerConfiguration.DownloadTimeoutSeconds), 5);
|
_queueExpirationSeconds = configurationService.GetValueOrDefault(nameof(StaticFilesServerConfiguration.DownloadTimeoutSeconds), 5);
|
||||||
_metrics = metrics;
|
_metrics = metrics;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_hubContext = hubContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<QueueStatus> EnqueueUser(UserRequest request)
|
public async Task EnqueueUser(UserRequest request)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Enqueueing req {guid} from {user} for {file}", request.RequestId, request.User, request.FileId);
|
_logger.LogDebug("Enqueueing req {guid} from {user} for {file}", request.RequestId, request.User, request.FileId);
|
||||||
|
|
||||||
if (_queueProcessingSemaphore.CurrentCount == 0)
|
if (_queueProcessingSemaphore.CurrentCount == 0)
|
||||||
{
|
{
|
||||||
_queue.Enqueue(request);
|
_queue.Enqueue(request);
|
||||||
return QueueStatus.Waiting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _queueSemaphore.WaitAsync().ConfigureAwait(false);
|
await _queueSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
QueueStatus status = QueueStatus.Waiting;
|
|
||||||
var idx = Array.FindIndex(_userQueueRequests, r => r == null);
|
var idx = Array.FindIndex(_userQueueRequests, r => r == null);
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
{
|
{
|
||||||
_queue.Enqueue(request);
|
_queue.Enqueue(request);
|
||||||
status = QueueStatus.Waiting;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DequeueIntoSlot(request, idx);
|
await DequeueIntoSlotAsync(request, idx).ConfigureAwait(false);
|
||||||
status = QueueStatus.Ready;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -66,22 +66,39 @@ public class RequestQueueService : IHostedService
|
|||||||
throw new Exception("Error during EnqueueUser");
|
throw new Exception("Error during EnqueueUser");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RemoveFromQueue(Guid requestId, string user)
|
||||||
|
{
|
||||||
|
if (!_queue.Any(f => f.RequestId == requestId && string.Equals(f.User, user, StringComparison.Ordinal)))
|
||||||
|
{
|
||||||
|
var activeSlot = _userQueueRequests.FirstOrDefault(r => r != null && string.Equals(r.UserRequest.User, user, StringComparison.Ordinal) && r.UserRequest.RequestId == requestId);
|
||||||
|
if (activeSlot != null)
|
||||||
|
{
|
||||||
|
var idx = Array.IndexOf(_userQueueRequests, activeSlot);
|
||||||
|
if (idx >= 0)
|
||||||
|
{
|
||||||
|
_userQueueRequests[idx] = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_queueRemoval[requestId] = user;
|
||||||
|
}
|
||||||
|
|
||||||
public bool StillEnqueued(Guid request, string user)
|
public bool StillEnqueued(Guid request, string user)
|
||||||
{
|
{
|
||||||
return _queue.FirstOrDefault(c => c.RequestId == request && string.Equals(c.User, user, StringComparison.Ordinal)) != null;
|
return _queue.Any(c => c.RequestId == request && string.Equals(c.User, user, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsActiveProcessing(Guid request, string user, out UserRequest userRequest)
|
public bool IsActiveProcessing(Guid request, string user, out UserRequest userRequest)
|
||||||
{
|
{
|
||||||
var userQueueRequest = _userQueueRequests.Where(u => u != null)
|
var userQueueRequest = _userQueueRequests.FirstOrDefault(u => u != null && u.UserRequest.RequestId == request && string.Equals(u.UserRequest.User, user, StringComparison.Ordinal));
|
||||||
.FirstOrDefault(f => f.UserRequest.RequestId == request && string.Equals(f.UserRequest.User, user, StringComparison.Ordinal));
|
|
||||||
userRequest = userQueueRequest?.UserRequest ?? null;
|
userRequest = userQueueRequest?.UserRequest ?? null;
|
||||||
return userQueueRequest != null && userRequest != null && userQueueRequest.ExpirationDate > DateTime.UtcNow;
|
return userQueueRequest != null && userRequest != null && userQueueRequest.ExpirationDate > DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FinishRequest(Guid request)
|
public void FinishRequest(Guid request)
|
||||||
{
|
{
|
||||||
var req = _userQueueRequests.Where(f => f != null).First(f => f.UserRequest.RequestId == request);
|
var req = _userQueueRequests.First(f => f != null && f.UserRequest.RequestId == request);
|
||||||
var idx = Array.IndexOf(_userQueueRequests, req);
|
var idx = Array.IndexOf(_userQueueRequests, req);
|
||||||
_logger.LogDebug("Finishing Request {guid}, clearing slot {idx}", request, idx);
|
_logger.LogDebug("Finishing Request {guid}, clearing slot {idx}", request, idx);
|
||||||
_userQueueRequests[idx] = null;
|
_userQueueRequests[idx] = null;
|
||||||
@@ -90,7 +107,7 @@ public class RequestQueueService : IHostedService
|
|||||||
public void ActivateRequest(Guid request)
|
public void ActivateRequest(Guid request)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Activating request {guid}", request);
|
_logger.LogDebug("Activating request {guid}", request);
|
||||||
_userQueueRequests.Where(f => f != null).First(f => f.UserRequest.RequestId == request).IsActive = true;
|
_userQueueRequests.First(f => f != null && f.UserRequest.RequestId == request).IsActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ProcessQueue(object src, ElapsedEventArgs e)
|
private async void ProcessQueue(object src, ElapsedEventArgs e)
|
||||||
@@ -103,9 +120,9 @@ public class RequestQueueService : IHostedService
|
|||||||
{
|
{
|
||||||
Parallel.For(0, _userQueueRequests.Length, new ParallelOptions()
|
Parallel.For(0, _userQueueRequests.Length, new ParallelOptions()
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = 10
|
MaxDegreeOfParallelism = 10,
|
||||||
},
|
},
|
||||||
(i) =>
|
async (i) =>
|
||||||
{
|
{
|
||||||
if (!_queue.Any()) return;
|
if (!_queue.Any()) return;
|
||||||
|
|
||||||
@@ -113,9 +130,25 @@ public class RequestQueueService : IHostedService
|
|||||||
|
|
||||||
if (_userQueueRequests[i] == null)
|
if (_userQueueRequests[i] == null)
|
||||||
{
|
{
|
||||||
if (_queue.TryDequeue(out var request))
|
bool enqueued = false;
|
||||||
|
while (!enqueued)
|
||||||
{
|
{
|
||||||
DequeueIntoSlot(request, i);
|
if (_queue.TryDequeue(out var request))
|
||||||
|
{
|
||||||
|
if (_queueRemoval.TryGetValue(request.RequestId, out string user) && string.Equals(user, request.User, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Request cancelled: {requestId} by {user}", request.RequestId, user);
|
||||||
|
_queueRemoval.Remove(request.RequestId, out _);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DequeueIntoSlotAsync(request, i).ConfigureAwait(false);
|
||||||
|
enqueued = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enqueued = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -133,10 +166,11 @@ public class RequestQueueService : IHostedService
|
|||||||
_metrics.SetGaugeTo(MetricsAPI.GaugeDownloadQueue, _queue.Count);
|
_metrics.SetGaugeTo(MetricsAPI.GaugeDownloadQueue, _queue.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DequeueIntoSlot(UserRequest userRequest, int slot)
|
private async Task DequeueIntoSlotAsync(UserRequest userRequest, int slot)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Dequeueing {req} into {i}: {user} with {file}", userRequest.RequestId, slot, userRequest.User, userRequest.FileId);
|
_logger.LogDebug("Dequeueing {req} into {i}: {user} with {file}", userRequest.RequestId, slot, userRequest.User, userRequest.FileId);
|
||||||
_userQueueRequests[slot] = new(userRequest, DateTime.UtcNow.AddSeconds(_queueExpirationSeconds));
|
_userQueueRequests[slot] = new(userRequest, DateTime.UtcNow.AddSeconds(_queueExpirationSeconds));
|
||||||
|
await _hubContext.Clients.User(userRequest.User).SendAsync(nameof(IMareHub.Client_DownloadReady), userRequest.RequestId).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Grpc.Net.Client.Configuration;
|
using Grpc.Net.Client.Configuration;
|
||||||
using Grpc.Net.ClientFactory;
|
using Grpc.Net.ClientFactory;
|
||||||
|
using MareSynchronos.API;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
@@ -10,10 +11,12 @@ using MareSynchronosStaticFilesServer.Utils;
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
|
using StackExchange.Redis;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer;
|
namespace MareSynchronosStaticFilesServer;
|
||||||
@@ -162,6 +165,19 @@ public class Startup
|
|||||||
|
|
||||||
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
|
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
|
||||||
|
|
||||||
|
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||||
|
var signalRServiceBuilder = services.AddSignalR(hubOptions =>
|
||||||
|
{
|
||||||
|
hubOptions.MaximumReceiveMessageSize = long.MaxValue;
|
||||||
|
hubOptions.EnableDetailedErrors = true;
|
||||||
|
hubOptions.MaximumParallelInvocationsPerClient = 10;
|
||||||
|
hubOptions.StreamBufferCapacity = 200;
|
||||||
|
});
|
||||||
|
|
||||||
|
// configure redis for SignalR
|
||||||
|
var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
|
||||||
|
signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options => { });
|
||||||
|
|
||||||
services.AddHealthChecks();
|
services.AddHealthChecks();
|
||||||
services.AddControllers();
|
services.AddControllers();
|
||||||
}
|
}
|
||||||
@@ -188,6 +204,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
e.MapGrpcService<GrpcFileService>();
|
e.MapGrpcService<GrpcFileService>();
|
||||||
}
|
}
|
||||||
|
e.MapHub<MareSynchronosServer.Hubs.MareHub>("/dummyhub");
|
||||||
e.MapControllers();
|
e.MapControllers();
|
||||||
e.MapHealthChecks("/health").WithMetadata(new AllowAnonymousAttribute());
|
e.MapHealthChecks("/health").WithMetadata(new AllowAnonymousAttribute());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,25 +35,43 @@ public class RequestFileStreamResult : FileStreamResult
|
|||||||
|
|
||||||
public override void ExecuteResult(ActionContext context)
|
public override void ExecuteResult(ActionContext context)
|
||||||
{
|
{
|
||||||
base.ExecuteResult(context);
|
try
|
||||||
|
{
|
||||||
|
base.ExecuteResult(context);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_releaseCts.Cancel();
|
||||||
|
|
||||||
_releaseCts.Cancel();
|
if (!_releasedSlot)
|
||||||
|
_requestQueueService.FinishRequest(_requestId);
|
||||||
|
|
||||||
if (!_releasedSlot)
|
_mareMetrics.DecGauge(MetricsAPI.GaugeCurrentDownloads);
|
||||||
_requestQueueService.FinishRequest(_requestId);
|
}
|
||||||
|
|
||||||
_mareMetrics.DecGauge(MetricsAPI.GaugeCurrentDownloads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteResultAsync(ActionContext context)
|
public override async Task ExecuteResultAsync(ActionContext context)
|
||||||
{
|
{
|
||||||
await base.ExecuteResultAsync(context).ConfigureAwait(false);
|
try
|
||||||
|
{
|
||||||
|
await base.ExecuteResultAsync(context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_releaseCts.Cancel();
|
||||||
|
|
||||||
_releaseCts.Cancel();
|
if (!_releasedSlot)
|
||||||
|
_requestQueueService.FinishRequest(_requestId);
|
||||||
|
|
||||||
if (!_releasedSlot)
|
_mareMetrics.DecGauge(MetricsAPI.GaugeCurrentDownloads);
|
||||||
_requestQueueService.FinishRequest(_requestId);
|
}
|
||||||
|
|
||||||
_mareMetrics.DecGauge(MetricsAPI.GaugeCurrentDownloads);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user