Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The ApmAsyncFactory interop hangs if a path in async method returns synchronously. #281

Open
fiseni opened this issue Oct 18, 2023 · 1 comment · May be fixed by #282
Open

The ApmAsyncFactory interop hangs if a path in async method returns synchronously. #281

fiseni opened this issue Oct 18, 2023 · 1 comment · May be fixed by #282
Assignees

Comments

@fiseni
Copy link

fiseni commented Oct 18, 2023

Hi,

I observed a strange behavior in ApmAsyncFactory. If the task contains a path that returns synchronously, then the execution hangs. It's easy to reproduce, so I'll just provide an example. I'll use asmx web service to demonstrate the issue.

public class DemoService : System.Web.Services.WebService
{

    [WebMethod]
    public IAsyncResult BeginRun(int option, AsyncCallback callback, object state)
    {
        return ApmAsyncFactory.ToBegin<string>(GetResponseAsync(option), callback, state);
    }
    [WebMethod]
    public string EndRun(IAsyncResult result)
    {
        return ApmAsyncFactory.ToEnd<string>(result);
    }

    private async Task<string> GetResponseAsync(int option)
    {
        if (option == 1)
        {
            await Task.Delay(1000);
            return "Works";
        }
        else if (option == 2)
        {
            // I tried awaiting a completed task too, just to test it out
            await Task.CompletedTask;
            return "Hangs";
        }
        else
        {
            return "Hangs";
        }
    }
}

To be sure that this is not a generic problem, I tried the solution provided in this article, and it works without issues.

public class DemoService : System.Web.Services.WebService
{

    [WebMethod]
    public IAsyncResult BeginRun(int option, AsyncCallback callback, object state)
    {
        return GetResponseAsync(option).AsApm(callback, state);
    }
    [WebMethod]
    public string EndRun(IAsyncResult result)
    {
        return ((Task<string>)result).Result;
    }

    private async Task<string> GetResponseAsync(int option)
    {
        if (option == 1)
        {
            await Task.Delay(1000);
            return "Works";
        }
        else if (option == 2)
        {

            await Task.CompletedTask;
            return "Works";
        }
        else
        {
            return "Works";
        }
    }
}
@fiseni
Copy link
Author

fiseni commented Oct 18, 2023

It seems there is a race condition. If there is an asynchronous execution, the ToBegin returns before the callback is invoked. On the other hand, if the task is already completed, the callback is invoked before the ToBegin manages to return a result.

image

Just to confirm this, I tried adding a small delay before the callback is invoked, and it works properly then.

EDIT: There is no even race condition here. If the task is already completed, CompleteAsync will run synchronously, and a callback always will be invoked before ToBegin returns;

@StephenCleary StephenCleary self-assigned this Oct 18, 2023
@fiseni fiseni linked a pull request Oct 20, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants