Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR LongPolling multiple Groups.Add for a single client Exception

I've been struggling with this one for a while. We're using the latest SignalR 2.0.3. The problem happens when we add to multiple SignalR groups. The exception is only thrown when multiple adds happen from the same connectionId with different group names. The exception is only thrown if the LongPolling transport is selected. The exception is only thrown if you add to 6+ unique group names, 5 or less and it works fine.

Here's a simplified example:

Index.cshtml:

    @model Int32?
    <!DOCTYPE html>
    <head>
        <title></title>
<script src="@Url.Content("~/Scripts/jquery.min.js")" type="text/javascript"/>
<script src="@Url.Content("~/Scripts/jquery.signalR-2.0.3.min.js")"
    type="text/javascript" />
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"/>
    </head>

    <script>
        _testHub = $.connection.testHub;
        _testHub.client.sayHello = sayHello;
        $.connection.hub.start({ transport: 'longPolling' })
            .done(function() {
                addAllGroups();
            });

        function sayHello(aMessage, aGroupName) {
            console.info("GroupName: " + aGroupName 
    + " Message Sent:" + aMessage);
        };

        function addAllGroups() {
            for (var i = 0; 
              i < @(Model.HasValue ? Model.Value : 1 ); i++) {
                  addToGroupAndBroadcast(i);
            }
        };

        function addToGroupAndBroadcast(aGroupName) {
            _testHub.server.addToGroupAndBroadcast(aGroupName)
                .fail(function (desc) {
                    console.info("Error: " + desc);
                });
        };

    </script>

SignalRTestController.cs:

using System;
using System.Web.Mvc;


namespace Instrumar.ProductionDashboard.Controllers
{
    public class SignalRTestController : Controller
    {
        #region Public Members

        public ActionResult Index()
        {
            return View((int?)Convert.ToInt32(Request.Url.Segments[4]));
        }

        #endregion
    }
}

TestHub.cs:

using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;


namespace Instrumar.ProductionDashboard.Hubs
{
    public class TestHub : Hub
    {
        #region Public Members

        public void AddToGroupAndBroadcast(string aGroupName)
        {
            GlobalHost.ConnectionManager.GetHubContext<TestHub>().Groups.Add(Context.ConnectionId, aGroupName).Wait();
            Clients.Group(aGroupName).sayHello("Hello", aGroupName);
        }

        #endregion
    }
}

Startup.cs:

using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SignalRChat.Startup))]
namespace SignalRChat
{

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var lHubConfiguration = new HubConfiguration {EnableDetailedErrors = true};
            app.UseErrorPage();
            app.MapSignalR(lHubConfiguration);
        }
    }
}

The controller takes an integer as input which is the number of Add's to do. For example, if you call:

http://yourip.com/WebApplicationName/SignalRTest/Index/1 one Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/2 two Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/3 three Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/4 four Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/5 five Groups.Add works fine http://yourip.com/WebApplicationName/SignalRTest/Index/6 six Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/7 seven Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/8 eight Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/9 nine Groups.Add broken http://yourip.com/WebApplicationName/SignalRTest/Index/10 ten Groups.Add broken

...

When I say broken, I get back a "System.Threading.Tasks.TaskCanceledException" in TestHub.cs on the line waiting for the task to complete. Everything works fine with ServerSentEvents but for LongPolling the problem exists. What am I doing wrong? Can I not have more than 5 SignalR groups with LongPolling? Help me! :)


Update: Putting a 1 millisecond sleep using setTimeout between clientside calls fixed the problem. This seems to alleviate the number of pending connections in the network tab. Maybe something happens to the ability to add to a group when you reach the single browser connection limit. It would be nice to know exactly why this doesn't work though.

like image 875
Adam Avatar asked Jun 12 '14 19:06

Adam


People also ask

How many connections can SignalR handle?

In the default mode, the app server creates five server connections with Azure SignalR Service. The app server uses the Azure SignalR Service SDK by default. In the following performance test results, server connections are increased to 15 (or more for broadcasting and sending a message to a big group).

Is SignalR full duplex?

SignalR supports full duplex communication that allows the client and server to transfer data back and forth.

What is long polling in SignalR?

SignalR is a Microsoft framework specifically designed to facilitate real-time client/server communication. It provides an effective implementation of long polling that doesn't have a deep impact on the server, and at the same time ensures that clients are appropriately updated about what's going on remotely.

What is hubName in SignalR?

SignalR Hubs are a way to logically group connections as documented. Take an example of a chat application. A group of users could be part of the same hub which allows for sending messages between users in the group. The hubName here can be any string which is used to scope messages being sent between clients.


1 Answers

You can have more than 5 groups with long-polling, but you'll have to work around the connection issue by not adding to groups in a loop.

The task returned from Groups.Add only completes when it receives an ACK message from the message bus indicating that the message for the client (notifying it of the group add) has been sent. If there are more than 5 (or whatever number is enforced by the browser) pending AJAX requests tied to the addToGroupAndBroadcast call from the client, then the message to the client can't be sent. If you attach a done callback to your client call:

_testHub.server.addToGroupAndBroadcast(groupName).done(function() {
    console.log("complete");
});

you'll see the first call completes almost immediately.

This is when the first Groups.Add call returns. The message to sayHello doesn't get sent anymore because it would require another open polling request, which the client is creating, but won't get handled since there are multiple other pending requests from the client that have been enqueued first (and those are all waiting on addToGroupAndBroadcast to complete).

So after the first call to Groups.Add, the server is unable to send anything to the client, the MessageHub can't send its ACK which would resolve the remaining Groups.Add promises. As a result, the TaskCanceledException gets thrown for those Tasks because of a timeout.

As a work-around, I would suggest to create a hub method which accepts an array of group names:

public async Task AddToGroups(string[] names)
{
    foreach (var name in names)
    {
        await Groups.Add(Context.ConnectionId, name);
        Clients.Group(name).sayHello("Hello", name);
    }
}
like image 194
Lars Höppner Avatar answered Oct 07 '22 16:10

Lars Höppner