diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 0eedc7c949..2aa9916bf7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -710,7 +710,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Task.WhenAll( disconnectTask, listenTask ?? Task.CompletedTask), - AsyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken)); + AsyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).AsTask()); } /// diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs index ed5ee75a56..e7fbd9baa6 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs @@ -135,7 +135,7 @@ namespace Tgstation.Server.Host.Components.Engine const int MaximumTerminationSeconds = 5; logger.LogTrace("Attempting Robust.Server graceful exit (Timeout: {seconds}s)...", MaximumTerminationSeconds); - var timeout = asyncDelayer.Delay(TimeSpan.FromSeconds(MaximumTerminationSeconds), cancellationToken); + var timeout = asyncDelayer.Delay(TimeSpan.FromSeconds(MaximumTerminationSeconds), cancellationToken).AsTask(); var lifetime = process.Lifetime; if (lifetime.IsCompleted) logger.LogTrace("Robust.Server already exited"); diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index d178eb94e0..b45b33a6cf 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -522,30 +521,6 @@ namespace Tgstation.Server.Host.Components logger.LogInformation("Next auto-update will occur at {time}", DateTimeOffset.UtcNow + delay); - // https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=net-8.0#system-threading-tasks-task-delay(system-timespan) - const uint DelayMinutesLimit = UInt32.MaxValue - 1; - Debug.Assert(DelayMinutesLimit == 4294967294, "Delay limit assertion failure!"); - - var maxDelayIterations = 0UL; - if (delay.TotalMilliseconds >= UInt32.MaxValue) - { - maxDelayIterations = (ulong)Math.Floor(delay.TotalMilliseconds / DelayMinutesLimit); - logger.LogDebug("Breaking interval into {iterationCount} iterations", maxDelayIterations + 1); - delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds - (maxDelayIterations * DelayMinutesLimit)); - } - - if (maxDelayIterations > 0) - { - var longDelayTimeSpan = TimeSpan.FromMilliseconds(DelayMinutesLimit); - for (var i = 0UL; i < maxDelayIterations; ++i) - { - logger.LogTrace("Long delay #{iteration}...", i + 1); - await asyncDelayer.Delay(longDelayTimeSpan, cancellationToken); - } - - logger.LogTrace("Final delay iteration #{iteration}...", maxDelayIterations + 1); - } - await asyncDelayer.Delay(delay, cancellationToken); logger.LogInformation("Beginning auto update..."); await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), true, cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 5f6a101260..12ebb1300e 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -541,7 +541,7 @@ namespace Tgstation.Server.Host.Components loggedDelay = true; } - delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); + delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).AsTask(); } await delayTask; diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 77a25fd979..53f8086f07 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -547,7 +547,8 @@ namespace Tgstation.Server.Host.Components.Session toAwait, asyncDelayer.Delay( TimeSpan.FromSeconds(startupTimeout.Value), - CancellationToken.None)); // DCT: None available, task will clean up after delay + CancellationToken.None) + .AsTask()); // DCT: None available, task will clean up after delay Logger.LogTrace( "Waiting for LaunchResult based on {launchResultCompletionCause}{possibleTimeout}...", @@ -611,7 +612,7 @@ namespace Tgstation.Server.Host.Components.Session const int GracePeriodSeconds = 30; Logger.LogDebug("Server will terminated in {gracePeriodSeconds}s if it does not exit...", GracePeriodSeconds); - var delayTask = asyncDelayer.Delay(TimeSpan.FromSeconds(GracePeriodSeconds), CancellationToken.None); // DCT: None available + var delayTask = asyncDelayer.Delay(TimeSpan.FromSeconds(GracePeriodSeconds), CancellationToken.None).AsTask(); // DCT: None available await Task.WhenAny(process.Lifetime, delayTask); if (!process.Lifetime.IsCompleted) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs index b58db53601..68e44eee5d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs @@ -185,7 +185,7 @@ namespace Tgstation.Server.Host.Components.Watchdog lingeringDeploymentExpirySeconds); // DCT: A cancel firing here can result in us leaving a dmbprovider undisposed, localDeploymentCleanupGate will always fire in that case - var timeout = AsyncDelayer.Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), CancellationToken.None); + var timeout = AsyncDelayer.Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), CancellationToken.None).AsTask(); var completedTask = await Task.WhenAny( localDeploymentCleanupGate.Task, diff --git a/src/Tgstation.Server.Host/Security/SessionInvalidationTracker.cs b/src/Tgstation.Server.Host/Security/SessionInvalidationTracker.cs index 6f302a3532..00880fc1b3 100644 --- a/src/Tgstation.Server.Host/Security/SessionInvalidationTracker.cs +++ b/src/Tgstation.Server.Host/Security/SessionInvalidationTracker.cs @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Security var timeTillSessionExpiry = authenticationContext.SessionExpiry - DateTimeOffset.UtcNow; if (timeTillSessionExpiry > TimeSpan.Zero) { - var delayTask = asyncDelayer.Delay(timeTillSessionExpiry, applicationLifetime.ApplicationStopping); + var delayTask = asyncDelayer.Delay(timeTillSessionExpiry, applicationLifetime.ApplicationStopping).AsTask(); await Task.WhenAny(delayTask, otherCancellationReason); diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs index b6a0e51df6..dcb3737ff9 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -298,7 +298,7 @@ namespace Tgstation.Server.Host.Swarm var timeoutTask = swarmController ? asyncDelayer.Delay( TimeSpan.FromMinutes(SwarmConstants.UpdateCommitTimeoutMinutes), - cancellationToken) + cancellationToken).AsTask() : Extensions.TaskExtensions.InfiniteTask.WaitAsync(cancellationToken); var commitTask = Task.WhenAny(localUpdateOperation.CommitGate, timeoutTask); @@ -1512,7 +1512,8 @@ namespace Tgstation.Server.Host.Swarm var delayTask = asyncDelayer.Delay( delay, - cancellationToken); + cancellationToken) + .AsTask(); var awakeningTask = Task.WhenAny( delayTask, diff --git a/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs b/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs index 79ffce1838..c8adc33436 100644 --- a/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs +++ b/src/Tgstation.Server.Host/Utils/AsyncDelayer.cs @@ -1,13 +1,57 @@ using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + namespace Tgstation.Server.Host.Utils { /// sealed class AsyncDelayer : IAsyncDelayer { + /// + /// The for the . + /// + readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + public AsyncDelayer(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + /// - public Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken) => Task.Delay(timeSpan, cancellationToken); + public async ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken) + { + // https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=net-8.0#system-threading-tasks-task-delay(system-timespan) + const uint DelayMinutesLimit = UInt32.MaxValue - 1; + Debug.Assert(DelayMinutesLimit == 4294967294, "Delay limit assertion failure!"); + + var maxDelayIterations = 0UL; + if (timeSpan.TotalMilliseconds >= UInt32.MaxValue) + { + maxDelayIterations = (ulong)Math.Floor(timeSpan.TotalMilliseconds / DelayMinutesLimit); + logger.LogDebug("Breaking interval into {iterationCount} iterations", maxDelayIterations + 1); + timeSpan = TimeSpan.FromMilliseconds(timeSpan.TotalMilliseconds - (maxDelayIterations * DelayMinutesLimit)); + } + + if (maxDelayIterations > 0) + { + var longDelayTimeSpan = TimeSpan.FromMilliseconds(DelayMinutesLimit); + for (var i = 0UL; i < maxDelayIterations; ++i) + { + logger.LogTrace("Long delay #{iteration}...", i + 1); + await Task.Delay(longDelayTimeSpan, cancellationToken); + } + + logger.LogTrace("Final delay iteration #{iteration}...", maxDelayIterations + 1); + } + + await Task.Delay(timeSpan, cancellationToken); + } } } diff --git a/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs b/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs index d47c5cbc62..e60799fc29 100644 --- a/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs +++ b/src/Tgstation.Server.Host/Utils/IAsyncDelayer.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Utils /// /// The that must elapse. /// The for the operation. - /// A representing the running operation. - Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken); } } diff --git a/tests/Tgstation.Server.Host.Tests.Signals/Program.cs b/tests/Tgstation.Server.Host.Tests.Signals/Program.cs index 92a733e59d..bcb5aa6699 100644 --- a/tests/Tgstation.Server.Host.Tests.Signals/Program.cs +++ b/tests/Tgstation.Server.Host.Tests.Signals/Program.cs @@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Tests.Signals .Returns(ValueTask.CompletedTask); var mockAsyncDelayer = new Mock(); - mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(ValueTask.CompletedTask); using var signalHandler = new PosixSignalHandler(mockServerControl.Object, mockAsyncDelayer.Object, Mock.Of>()); Assert.IsFalse(tcs.Task.IsCompleted); diff --git a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs index bc1ecbf0a4..834be3bcad 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Chat/Providers/TestIrcProvider.cs @@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests Instance = new Models.Instance(), }; - await using var provider = new IrcProvider(mockJobManager, new AsyncDelayer(), loggerFactory.CreateLogger(), Mock.Of(), chatBot, new FileLoggingConfiguration()); + await using var provider = new IrcProvider(mockJobManager, new AsyncDelayer(loggerFactory.CreateLogger()), loggerFactory.CreateLogger(), Mock.Of(), chatBot, new FileLoggingConfiguration()); Assert.IsFalse(provider.Connected); await InvokeConnect(provider); Assert.IsTrue(provider.Connected); diff --git a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs index 5598673dc2..fba28a4d6e 100644 --- a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs @@ -91,7 +91,7 @@ namespace Tgstation.Server.Host.Setup.Tests mockInternalConfigurationOptions.Object); mockPlatformIdentifier.SetupGet(x => x.IsWindows).Returns(true).Verifiable(); - mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask).Verifiable(); + mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(ValueTask.CompletedTask).Verifiable(); await RunWizard(); diff --git a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs index c5f22614b8..9c130af80e 100644 --- a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs +++ b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs @@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Swarm.Tests mockAsyncDelayer.Setup( x => x.Delay(It.IsAny(), It.IsAny())) .Returns( - (delay, ct) => Task.Delay(TimeSpan.FromMilliseconds(100), ct)); + async (delay, ct) => await Task.Delay(TimeSpan.FromMilliseconds(100), ct)); var mockServerUpdater = new Mock(); @@ -152,7 +152,7 @@ namespace Tgstation.Server.Host.Swarm.Tests new CryptographySuite( Mock.Of>()), Mock.Of(), - new AsyncDelayer(), // use a real one here because otherwise tickets expire too fast + new AsyncDelayer(Mock.Of>()), // use a real one here because otherwise tickets expire too fast CreateLoggerFactoryForLogger(loggerFactory.CreateLogger($"FileTransferService-{swarmConfiguration.Identifier}"), out var mockLoggerFactory).CreateLogger()); RpcMapper = new SwarmRpcMapper( diff --git a/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs b/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs index fcad8785c5..991a03ceb3 100644 --- a/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs +++ b/tests/Tgstation.Server.Host.Tests/Utils/TestAsyncDelayer.cs @@ -2,8 +2,11 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + namespace Tgstation.Server.Host.Utils.Tests { [TestClass] @@ -12,7 +15,7 @@ namespace Tgstation.Server.Host.Utils.Tests [TestMethod] public async Task TestDelay() { - var delayer = new AsyncDelayer(); + var delayer = new AsyncDelayer(Mock.Of>()); var startDelay = delayer.Delay(TimeSpan.FromSeconds(1), CancellationToken.None); var checkDelay = Task.Delay(TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(100), CancellationToken.None); await startDelay; @@ -22,10 +25,10 @@ namespace Tgstation.Server.Host.Utils.Tests [TestMethod] public async Task TestCancel() { - var delayer = new AsyncDelayer(); + var delayer = new AsyncDelayer(Mock.Of>()); using var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token)); + await Assert.ThrowsExceptionAsync(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token).AsTask()); } } } diff --git a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs index 872e59e6e6..b82cf58226 100644 --- a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs +++ b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs @@ -51,7 +51,7 @@ namespace Tgstation.Server.Tests.Live // at time of writing, this is used exclusively for the reconnection interval which works in minutes // shorten it to 3s var mock = new Mock(); - mock.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns((delay, cancellationToken) => Task.Delay(TimeSpan.FromSeconds(3), cancellationToken)); + mock.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(async (delay, cancellationToken) => await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken)); return mock.Object; } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 16d3ac63fa..aff9e45867 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -309,7 +309,7 @@ namespace Tgstation.Server.Tests.Live.Instance ? await session.TopicSendSemaphore.Lock(cancellationToken) : null) return await topicClient.SendWithOptionalPriority( - new AsyncDelayer(), + new AsyncDelayer(loggerFactory.CreateLogger()), loggerFactory.CreateLogger(), queryString, topicPort,