Skip to content

Undocumented behavior: DNS exception thrown from HttpClient changes threads #9684

Open
@forrestcavalier

Description

@forrestcavalier

Can the documentation be improved to mention this? Maybe I am going crazy, or my code is wrong, but this caused me hours of frustration, (looking for documentation, reviewing my code, and trying different things.)

Worker is started by Task.Factory.StartNew(SAPClientLoop.Worker); The caller was expecting Task.IsCompleted to stay False as long as the worker loop was running, but after the DNS WebException, Task.IsCompleted went to True even though the worker loop was still running, just on a different thread!

If this is fixed in later .NET, that doesn't help me. (Someone else decided Framework 4.8 for the project.)

Source code comments have more explanation of what I think is happening.

/// <summary>
/// The HttpClient worker thread
/// </summary>
public static async void Worker()
{
    //Notes from testing on Framework 4.8
    //Note: await async and HttpClient.SendAsync use a Thread pool.
    //When an exception is thrown from HttpClient (e.g. DNS lookup error),
    //from within a try-catch block, the catch() block may (will?) be
    //resumed using a different Thread, after the original thread is just
    //stopped without reaching any finally clause. The caller's reference to
    //the Task.IsCompleted returns True (would Thread.IsAlive returns False?)
    //even though WorkerLoop is still running on the different thread!
    //
    //It is unknown how any owned synchronization objects would get
    //retained or released when the original thread is stopped. To work
    //around this behavior, it seems the caller must not use any Task or Thread
    //objects, and maintain a WorkerRunning status flag which is
    //get, private set so that the caller can test it.
    try
    {
        WorkerRunning = true;
        await WorkerLoop(); // Calls .SendAsync with a ".invalid" domain inside a try-catch
        WorkerRunning = false; //Not hit
    }
    catch
    {
        WorkerRunning = false; //Not hit
    }
    finally
    {
        WorkerRunning = false; //Not hit
    }
}

My WorkerLoop() has logging, this is what is seen. ("[2]" shown in the log entries is the operation ID,
and "startup" is the operation name. Startup is implemented by "Authorize()")

03/06/2024 07:57:46.359 AM,WorkerLoop(),ManagedThreadId =8 IsThreadPoolThread=True
03/06/2024 08:01:52.677 AM,Log(),start[2] (pending 0) startup
03/06/2024 08:01:52.677 AM,ClientLoop.Authorize(),API startup
System.Net.WebException: The remote name could not be resolved: 'api.com.invalid'
at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)
at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
03/06/2024 08:02:02.005 AM,ClientLoop.Authorize(),API failed.
03/06/2024 08:02:03.794 AM,Log(),finish[2] (pending 0) startup
03/06/2024 08:02:03.794 AM,WorkerLoop(),ManagedThreadId =4 IsThreadPoolThread=True

Metadata

Metadata

Assignees

No one assigned

    Labels

    Pri3Indicates issues/PRs that are low priorityarea-System.Threading.TasksuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions