Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

awaiting a HubProxy.Invoke method makes the HubConnection.Dispose take 30 seconds to dispose #2653

Closed
maboivin opened this Issue · 5 comments

5 participants

@maboivin

I have a simple Hub in a an ASP.NET MVC5 web app. I'm using SignalR 2.0.0.

public class MyHub : Hub
{
    public void SendMessage(string message)
    {
        this.Clients.All.OnMessage(message);
    }
}

I have a simple client in .NET 4.5.

class Program
{
    static void Main(string[] args)
    {
        Run();

        Console.ReadKey();
    }

    static async void Run()
    {
        var url = "http://localhost:65479/";

        var tcs = new TaskCompletionSource<bool>();
        var sw = new Stopwatch();

        using (var hubConnection = new HubConnection(url))
        {
            var hubProxy = hubConnection.CreateHubProxy("MyHub");
            await hubConnection.Start();

            hubProxy.On<string>(
                "OnMessage",
                 message =>
                 {
                     Console.WriteLine(message);

                     tcs.SetResult(true);
                 });

            // When awaiting this call, it takes 30 seconds to dispose the hub connection.
            /*await*/ hubProxy.Invoke("SendMessage", "my message");

            // EDITED: Replaced tcs.Task.Wait(30 * 1000); with the following:
            await tcs.Task;

            sw.Start();
        }

        sw.Stop();

        // It takes 30 seconds to dispose...
        Console.WriteLine(sw.Elapsed);
    }
}

My issue is that when I await the call to hubProxy.Invoke(), the hub connection takes 30 seconds to dispose (which is the default timeout). But if I don't await the same call, it disposes fine. I have a simple project if needed to repro.

What should I do to dispose the connection without having to wait 30 seconds? Is it a bug or is my code wrong?

@davidfowl
Owner

Try doing:

await tcs.Task

It's bad to mix sync and async code.

@maboivin

Sorry, bad copy/paste. I previously tried it with .NET 4.0 and it was working fine without async calls. The original code was like you said "await tcs.Task" and it doesn't change anything, it still takes 30 seconds to dispose. I also tried the following code:

await tcs.Task.TimeoutAfter(30 * 1000);

and

static class TaskExtensions
{
    public static async Task<T> TimeoutAfter<T>(this Task<T> task, int timeoutInMillis)
    {
        using (var cancellationTokenSource = new CancellationTokenSource())
        {
            if (task == await Task.WhenAny(task, Task.Delay(timeoutInMillis, cancellationTokenSource.Token)))
            {
                cancellationTokenSource.Cancel();

                return await task;
            }
            else
            {
                throw new TimeoutException();
            }
        }
    }
}
@davidfowl
Owner

By default calling dispose/stop can take up to 30 seconds as it tries to abort the connection gracefully. For some reason that is hanging and we can look into why. I assume you're using 2.0.0 rtw?

@maboivin

You're right. I'm using 2.0.0 rtw. If you want me to upload a repro solution, let me know.

@halter73 halter73 was assigned
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Dispatch invocation callbacks to new thread in .NET client
- This fixes the issue with awaiting an Invoke call and then calling Stop()
- #2653
7616f08
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Queue & dispatch all receives in the client
- This ensures that calling stop from a message receive doesn't deadlock the receive loop
- Using TaskQueue.cs so added that to client projects
- Had to modify TaskQueue to work in Portable, now do optimistic increment then unwind if we exceed the max size
- Added a test to verify that async connection start, invoke, stop works without dead-locking
- #2653
281d442
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Queue & dispatch all receives in the client
- This ensures that calling stop from a message receive doesn't deadlock the receive loop
- Using TaskQueue.cs so added that to client projects
- Had to modify TaskQueue to work in Portable, now do optimistic increment then unwind if we exceed the max size
- Added a test to verify that async connection start, invoke, stop works without dead-locking
- #2653
d6fad41
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Ensure receive queue drained on close
- Drain the receive queue when Stop called
- Raise connection error event if queued OnReceived callback throws
- Remove obsolete buffered messages tests
- #2653
e86ce61
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Removed duplicate call to start from failing test
- Will log a separate issue to track the reason why this causes the failure
- #2653
324b455
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Queue & dispatch all receives in the client
- This ensures that calling stop from a message receive doesn't deadlock the receive loop
- Using TaskQueue.cs so added that to client projects
- Had to modify TaskQueue to work in Portable, now do optimistic increment then unwind if we exceed the max size
- Added a test to verify that async connection start, invoke, stop works without dead-locking
- #2653
d0cea02
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Ensure receive queue drained on close
- Drain the receive queue when Stop called
- Raise connection error event if queued OnReceived callback throws
- Remove obsolete buffered messages tests
- #2653
e1b47cc
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Removed duplicate call to start from failing test
- Will log a separate issue to track the reason why this causes the failure
- #2653
f243c5a
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Brought back deleted test
- Tests that received messages are not processed until the start init message is received and that the execution order is correct
- #2653
5352eba
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Queue & dispatch all receives in the client
- This ensures that calling stop from a message receive doesn't deadlock the receive loop
- Using TaskQueue.cs so added that to client projects
- Had to modify TaskQueue to work in Portable, now do optimistic increment then unwind if we exceed the max size
- Added a test to verify that async connection start, invoke, stop works without dead-locking
- Drain the receive queue when Stop called
- Raise connection error event if queued OnReceived callback throws
- Removed duplicate call to start from failing test (Will log a separate issue to track the reason why this causes the failure)
- #2653
7b81cb3
@DamianEdwards DamianEdwards referenced this issue from a commit
@DamianEdwards DamianEdwards Ensure start task waits on messages sent from OnConnected
- This preserves the previous client behavior
- #2653
d4491a5
@gustavo-armenta

tested repro and now connection disposes in less than one second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.