Skip to content

NullReferenceException in ServiceExtensions.TimeoutAfter when cancellation occurs #289

@wazzamatazz

Description

@wazzamatazz

The ServiceExtensions.TimeoutAfter extension method throws a NullReferenceException if cancellation occurs. The exception occurs because the extension attempts to throw an Exception that is null:

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, CancellationToken token)
{
    var t = await Task.WhenAny(task, Task.Delay(timeout, token)).ConfigureAwait(false);
    if (task != t)
    {
        if (!t.IsCanceled)
        {
            throw new TimeoutException($"Task timed out after {timeout}");
        }

        // Error is here: t.Exception is always null if the Task.Delay task was cancelled
        throw t.Exception!;
    }

    return await task.ConfigureAwait(false);
}

Given that the only two possible states for the Task.Delay call if it completes first are completed and cancelled, would recommend changing the implementation to:

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, CancellationToken token)
{
    var t = await Task.WhenAny(task, Task.Delay(timeout, token)).ConfigureAwait(false);
    if (task != t)
    {
        if (t.IsCanceled) 
        {
            throw new OperationCanceledException(token);
        }

        throw new TimeoutException($"Task timed out after {timeout}");
    }

    return await task.ConfigureAwait(false);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions