dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.14k stars 4.71k forks source link

Task.Delay(Timeout.Infinite, cancellationToken) does not return when cancellation is requested #88044

Closed dav1dev closed 1 year ago

dav1dev commented 1 year ago

Describe the bug

The method call await Task.Delay(Timeout.Infinite, cancellationToken); does not behave as expected. I expect this task to complete when cancellation is requested. But it does never return.

To Reproduce

public class Repro
{
        [Fact]
        public async Task TaskDelay_Infinite_DoesNotReturnWhenCancelled()
        {
            var ctsTest = new CancellationTokenSource(TimeSpan.FromSeconds(5));
            var ctsInfinite = new CancellationTokenSource();
            var started = new ManualResetEventSlim();
            var done = new ManualResetEventSlim();
            var t1 = Task.Run(async () =>
                {
                    started.Set();
                    // >> wait for cancellation
                    await Task.Delay(Timeout.Infinite, ctsInfinite.Token);
                    done.Set();
                },
                ctsTest.Token);

            started.Wait(ctsTest.Token);
            AssertIsTrue(started.IsSet);
            AssertIsFalse(done.IsSet);

            await Task.Delay(100, ctsTest.Token);

            AssertIsFalse(done.IsSet);

           // >> request cancellation now
            ctsInfinite.Cancel();

            await Task.Delay(100, ctsTest.Token);

            try
            {
                // >> would expect delay to return after cancellation
                done.Wait(ctsTest.Token);
            }
            catch (OperationCanceledException)
            {
                throw new Exception("Reset event was not set as expected because Task.Delay never returned");
            }

            await t1.WaitAsync(ctsTest.Token);
            AssertIsTrue(t1.IsCompleted);
        }

        private static void AssertIsTrue(bool value)
        {
            if (!value)
            {
                throw new Exception("Expected value to be true but was false.");
            }
        }

        private static void AssertIsFalse(bool value)
        {
            if (value)
            {
                throw new Exception("Expected value to be false but was true.");
            }
        }
}

Exceptions (if any)

Task is hanging forever (starvation).

Further technical details

dotnet --info

.NET SDK: Version: 7.0.304 Commit: 7e794e2806 Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\7.0.304\ Host: Version: 7.0.7 Architecture: x64 Commit: 5b20af47d9 .NET SDKs installed: 6.0.410 [C:\Program Files\dotnet\sdk] 7.0.304 [C:\Program Files\dotnet\sdk] .NET runtimes installed: Microsoft.AspNetCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation] Environment variables: Not set global.json file: Not found Learn more: https://aka.ms/dotnet/info Download .NET: https://aka.ms/dotnet/download .NET SDK (reflecting any global.json): Version: 6.0.410 Commit: 28c7c894a3 Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\6.0.410\ Host: Version: 7.0.7 Architecture: x64 Commit: 5b20af47d9 .NET SDKs installed: 6.0.410 [C:\Program Files\dotnet\sdk] 7.0.304 [C:\Program Files\dotnet\sdk] .NET runtimes installed: Microsoft.AspNetCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation] Environment variables: Not set global.json file: C:\work\ECU300\global.json Learn more: https://aka.ms/dotnet/info Download .NET: https://aka.ms/dotnet/download

IDE

JetBrains Rider 2023.1.2 Build #RD-231.9011.39, built on May 17, 2023 Runtime version: 17.0.6+10-b829.9 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 10.0 .NET 7.0.2 (Server GC) GC: G1 Young Generation, G1 Old Generation Memory: 1500M Cores: 32 Non-Bundled Plugins: com.layoutmanager (1.4.0) com.4lex4.intellij.solarized (2.4.0) Abc.MoqComplete.Rider (2023.1.0.1) idea.plugin.protoeditor (231.8109.91) com.intellij.resharper.StructuredLogging (2023.1.0.296) com.intellij.resharper.azure (3.50.0.1595-2023.1)

dotnet-issue-labeler[bot] commented 1 year ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

KalleOlaviNiemitalo commented 1 year ago
// >> wait for cancellation
await Task.Delay(Timeout.Infinite, ctsInfinite.Token);
done.Set();

When ctsInfinite.Token is signalled, I expect that the task returned by Task.Delay should be cancelled and await should throw a TaskCanceledException, which would then prevent done.Set() from being executed.

Have you tried with a trycatch around the await? Alternatively, delete the done.Wait(ctsTest.Token) call and catch the OperationCanceledException from await t1.WaitAsync(ctsTest.Token) instead.

KalleOlaviNiemitalo commented 1 year ago

In .NET 8, ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing) could also work here. https://github.com/dotnet/runtime/issues/22144

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/area-system-threading-tasks See info in area-owners.md if you want to be subscribed.

Issue Details
### Describe the bug The method call `await Task.Delay(Timeout.Infinite, cancellationToken);` does not behave as expected. I expect this task to complete when cancellation is requested. But it does never return. ### To Reproduce ```c# public class Repro { [Fact] public async Task TaskDelay_Infinite_DoesNotReturnWhenCancelled() { var ctsTest = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var ctsInfinite = new CancellationTokenSource(); var started = new ManualResetEventSlim(); var done = new ManualResetEventSlim(); var t1 = Task.Run(async () => { started.Set(); // >> wait for cancellation await Task.Delay(Timeout.Infinite, ctsInfinite.Token); done.Set(); }, ctsTest.Token); started.Wait(ctsTest.Token); AssertIsTrue(started.IsSet); AssertIsFalse(done.IsSet); await Task.Delay(100, ctsTest.Token); AssertIsFalse(done.IsSet); // >> request cancellation now ctsInfinite.Cancel(); await Task.Delay(100, ctsTest.Token); try { // >> would expect delay to return after cancellation done.Wait(ctsTest.Token); } catch (OperationCanceledException) { throw new Exception("Reset event was not set as expected because Task.Delay never returned"); } await t1.WaitAsync(ctsTest.Token); AssertIsTrue(t1.IsCompleted); } private static void AssertIsTrue(bool value) { if (!value) { throw new Exception("Expected value to be true but was false."); } } private static void AssertIsFalse(bool value) { if (value) { throw new Exception("Expected value to be false but was true."); } } } ``` ### Exceptions (if any) Task is hanging forever (starvation). ### Further technical details
dotnet --info

.NET SDK: Version: 7.0.304 Commit: 7e794e2806 Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\7.0.304\ Host: Version: 7.0.7 Architecture: x64 Commit: 5b20af47d9 .NET SDKs installed: 6.0.410 [C:\Program Files\dotnet\sdk] 7.0.304 [C:\Program Files\dotnet\sdk] .NET runtimes installed: Microsoft.AspNetCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation] Environment variables: Not set global.json file: Not found Learn more: https://aka.ms/dotnet/info Download .NET: https://aka.ms/dotnet/download .NET SDK (reflecting any global.json): Version: 6.0.410 Commit: 28c7c894a3 Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\6.0.410\ Host: Version: 7.0.7 Architecture: x64 Commit: 5b20af47d9 .NET SDKs installed: 6.0.410 [C:\Program Files\dotnet\sdk] 7.0.304 [C:\Program Files\dotnet\sdk] .NET runtimes installed: Microsoft.AspNetCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.18 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation] Environment variables: Not set global.json file: C:\work\ECU300\global.json Learn more: https://aka.ms/dotnet/info Download .NET: https://aka.ms/dotnet/download

IDE

JetBrains Rider 2023.1.2 Build #RD-231.9011.39, built on May 17, 2023 Runtime version: 17.0.6+10-b829.9 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 10.0 .NET 7.0.2 (Server GC) GC: G1 Young Generation, G1 Old Generation Memory: 1500M Cores: 32 Non-Bundled Plugins: com.layoutmanager (1.4.0) com.4lex4.intellij.solarized (2.4.0) Abc.MoqComplete.Rider (2023.1.0.1) idea.plugin.protoeditor (231.8109.91) com.intellij.resharper.StructuredLogging (2023.1.0.296) com.intellij.resharper.azure (3.50.0.1595-2023.1)

Author: dav1dev
Assignees: -
Labels: `area-System.Threading.Tasks`, `untriaged`
Milestone: -
ghost commented 1 year ago

This issue has been marked needs-author-action and may be missing some important information.

dav1dev commented 1 year ago

Thank you for your response. You are absolutely right. It is essential to catch the OperationCancelledException, either on the Delay or on waiting for the task:

try
{
    await Task.Delay(Timeout.Infinite, ctsInfinite.Token);
}
catch (OperationCanceledException)
{
    // ...
}
// or else
try
{
    await t1.WaitAsync(ctsTest.Token);
}
catch (OperationCanceledException)
{
    // ...
}