I am hoping that someone out there has come across this or can at least spot what is wrong with my code.
I have created a browser based Chat application using MVC and SignalR, because I need a user to Login to Chat I have implemented a Register against the MVC controller which adds the User to an internal UserList
public static void Register(string userName, string userRole, string userBase, string userLocation)
{
var userInfo = (from user in UsersList where user.Name == userName select user).FirstOrDefault();
if (userInfo != null)
{
userInfo.Location = userLocation;
userInfo.Role = userRole;
userInfo.Base = userBase;
return;
}
var rgx = new Regex("[^a-zA-Z0-9 -]");
UsersList.Add(
new UserInfo
{
Name = userName,
NameId = rgx.Replace(userName, "").Replace(" ", ""),
Base = userBase,
Location = userLocation,
Role = userRole,
Group = "Cleo",
Container = userLocation,
ContainerId = rgx.Replace(userLocation, "").Replace(" ", "").Trim()
});
}
And from the client side I instantiate the chatHub
and then call Connect
$(function () {
var objHub = $.connection.chatHub;
loadClientMethods(objHub);
$.connection.hub.start().done(function () {
loadEvents(objHub);
});
function loadEvents(objHub) {
$('#hState').val('open');
objHub.server.connect(gloalUserNameId);
}
All of this works well when on a single server and all the connections are held in memory, BTW, this has been in production for over a year.
Now, I had a requirement to establish better resiliency for our Chat application, for example in a load balanced environment but since everything is in memory I immediately hit the problems where User A connected to Server X and User B connected to Server Y (neither could see the other, you can see the problem).
So after digging around on the web for many long hours I came across a couple of articles that talk about scaling out SignalR using either Redis or SQL. Since our company uses SQL and has a lot of experience with this I chose this article.
I included the nuget package and modified my Startup
class to the following
string connectionString = "Server=.;Trusted_Connection=True;Database=CleoChatHost;";
GlobalHost.DependencyResolver.UseSqlServer(connectionString);
app.MapSignalR();
I configured two websites in IIS on separate ports to simulate a load balanced environment for testing purposes, eg. http://localhost:21215/ and http://localhost:21216/ which I wil call SiteA and SiteB
The following happens when I run each site:
- Launch SiteA with ?userName=TestUserA, then launch SiteB with ?userName=TestUserB -- The result is that SiteA loads (initially with no contacts), then SiteB loads, SiteA user list is updated with TestUserB. SiteB has not users.
NOTE: Doing the opposite of the above gives the same result.
I am at a loss as to what the issue is, I have checked the SQL database and can see that the backplane has created the SQL tables and data is going into them. I have tried to also implement the Redis solution with the same issue as above.
Has anyone implemented this and come across the same problem? or could anyone shed a little light on what the issue might be please?
EDIT
After some further diagnosis/testing it appears that the problem may be related to the internal UserList
that is maintained on each server and I believe the sequence of events are as follows:
UserA
logs ontoSiteA
SiteA
addsUserA
to theUserList
onSiteA
SiteA
executeshub.Connect(...)
from the client which in turn executesClients.Client(...).DrawTree(...)
andClients.AllExcept(...).addUser(...)
- The preceding call updates
SiteA
user list, and notifies all connected clients of a new user SiteA
is connected and has no active usersUserB
logs ontoSiteB
SiteB
addsUserB
to theUserList
onSiteB
SiteB
executeshub.Connect(...)
from the client which in turn executesClients.Client(...).DrawTree(...)
andClients.AllExcept(...).addUser(...)
- The preceding call updates
SiteB
user list, and notifies all connected clients of a new user -- This executesaddUser
on theSiteA
client to addUserB
to the user list SiteB
is connected and has no active users, whileSiteA
has one active user
I believe the issue is that the SQL backplane is only going to deal with messages when they are sent and have affirmed this from the naming convention of the tables in the SQL database. The internal UserList
however is not synchronised between the load balanced servers. The fact that SiteA
is updated with UserB
is only due to the broadcast of addUser
when UserB
connected to SiteB
(meaning SiteB
only ever contains UserB
and SiteA
only ever contains UserA
)
So to update my question above, how would I go about maintaining the UserList
between servers? Would I use the SQL backplane to achieve this?