I'm creating a web dashboard that will display the status of our test environments.
I use a hub to connect the browser to the server and have a background task that polls the status of the environment. I only want to perform this check if at least one client is connected.
My hub looks a little like this:
public class StatusHub : Hub
{
private static int connectionCount = 0;
public override Task OnConnected()
{
Interlocked.Increment(ref connectionCount);
return base.OnConnected();
}
public override Task OnReconnected()
{
Interlocked.Increment(ref connectionCount);
return base.OnReconnected();
}
public override Task OnDisconnected()
{
Interlocked.Decrement(ref connectionCount);
return base.OnDisconnected();
}
// other useful stuff
}
This mainly works but sometimes OnConnected is called but OnDisconnected is not.
One specific case is if I open chrome and type the address of the page but don't actually navigate to it. It seems Chrome is pre-fetching the page and connecting, but never disconnecting.
So two questions:
Is this a good approach to counting connections (I'm never going to be running in a web farm environment)?
Will these zombied connections from Chrome eventually timeout (I tried setting timeouts very low but still didn't get a disconnect)?
The events will always fire. If they don't, file a bug with repro steps on github. To get a more accurate number, you can store a hashset of connection ids and get the count from that.
Related
I've configured Application Insights on my web application however the telemetry is full of 'failed' requests from SingalR which makes spotting genuine issues difficult (see screenshot below). I want to filter out all SignalR requests and not send them to AI.
I've created a Telemetry Processor (see code below based on this example from MS) and registered it in the ApplicationInsights.config file, this works as expected when debugging locally and prevents any SignalR events from being sent however it doesn't seem to have any effect once the web application published and running in IIS.
public class SignalRFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
public SignalRFilter(ITelemetryProcessor next)
{
this.Next = next;
}
public void Process(ITelemetry item)
{
if (!OKtoSend(item))
return;
else
this.Next.Process(item);
}
private bool OKtoSend(ITelemetry item)
{
if (item is RequestTelemetry && ((RequestTelemetry)item).Url.AbsolutePath.Contains("signalr"))
return false;
else
return true;
}
}
Any suggestions?
You're looking at these requests in Live Metrics Stream. For this particular view you can filter them out on demand. Feature is available in latest 2.4-beta (the stable version should be released soon). In this case you can click on Filter button and add proper filters to every stream. Similar behavior exists for charts:
Have you checked whether you see /signair/start in you Application Insights Analytics? It might be the case that you did filter them out successfully and they only show up in Live view.
I have a requirement to start a process on the server that may run for several minutes, so I was thinking of exposing the following hub method:-
public async Task Start()
{
await Task.Run(() => _myService.Start());
}
There would also be a Stop() method that allows a client to stop the running process, probably via a cancellation token. I've also omitted code that prevents it from being started if already running, error handling, etc.
Additionally, the long-running process will be collecting data which it needs to periodically broadcast back to the client(s), so I was wondering about using an event - something like this:-
public async Task Start()
{
_myService.AfterDataCollected += AfterDataCollectedHandler;
await Task.Run(() => _myService.Start());
_myService.AfterDataCollected -= AfterDataCollectedHandler;
}
private void AfterDataCollectedHandler(object sender, MyDataEventArgs e)
{
Clients.All.SendData(e.Data);
}
Is this an acceptable solution or is there a "better" way?
You don't need to use SignalR to start the work, you can use the applications already existing framework / design / API for this and only use SignalR for the pub sub part.
I did this for my current customers project, a user starts a work and all tabs belonging to that user is updated using signalr, I used a out sun library called SignalR.EventAggregatorProxy to abstract the domain from SignalR. Disclaimer : I'm the author of said library
http://andersmalmgren.com/2014/05/27/client-server-event-aggregation-with-signalr/
edit: Using the .NET client your code would look something like this
public class MyViewModel : IHandle<WorkProgress>
{
public MyViewModel(IEventAggregator eventAggregator)
{
eventAggregator.Subscribe(this);
}
public void Handle(WorkProgress message)
{
//Act on work progress
}
}
I have the following TestHub that does nothing but invoke a client side sayHello() function.
public class TestHub:Hub
{
public void SayHello()
{
Context.Clients.All.sayHello();
}
public override Task OnConnected()
{
//do some custom stuff
return base.OnConnected();
}
}
On the client, I have the following:
var connection = $.hubConnection('http://localhost:12345/signalr');
var proxy = connection.createHubProxy('TestHub');
proxy.on('sayHello',function(){
console.log('sayHello fired');
})
connection.start().done(function(){
console.debug('Connected');
})
When I call SayHello() on my TestHub the client prints out the following perfectly fine
sayHelloFired
and when the proxy first loads, it prints the following to the console perfectly fine.
Connected
However, if I throw a breakpoint in the OnConnected() method on TestHub, then it does not hit.
All the posts discussing similar problems suggest that their is no handlers being subscribed on the client side, but that is not the case here. How could this be working and OnConnected() is never fired?
Your code didn't compile for me because of the
Context.Clients.All.sayHello();
Line. Context here tries to refer to Hub.Context which is an HubCallerContext class. I think you want to refer to Hub.Clients which is an IHubCallerConnectionContext.
I replaced that line to
Clients.All.sayHello();
And my breakpoint got invoked.
However I am surprised you made it run and got messages on console.
BTW You are right about that, when using javascript clients, if there is no subscription to any event in the hub, the OnConnected methond won't be invoked.
But that is not the case here.
Whenever my application gets reset, signalR disconnects but does not reconnect.
I have a long running server task which sends updates to clients when each task is completed.
// inside action executed on every completion of a task
var h = new ForceHub();
h.MessageSent(email);
above code stops sending updates when application gets reset (i can emulate this problem by touching web.config).
I'd like a way to reconnect to a client. Currently the user has to reload the page for it to get updates again.
Here is my hub definition
public class ForceHub : Hub
{
public void MessageSent(string text)
{
GetContext().Clients.All.sent(text);
}
public void UpdateStatus(string msg)
{
GetContext().Clients.All.status(msg);
}
IHubContext GetContext()
{
return GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
}
public override Task OnConnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Connected -----------");
}catch (Exception){}
return base.OnConnected();
}
public override Task OnDisconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Disconnected -----------");
}
catch (Exception) { }
return base.OnDisconnected();
}
public override Task OnReconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Re-Connected -----------");
}
catch (Exception) { }
return base.OnReconnected();
}
}
I can see Connected and Re-Connected events triggered after startup, however after touching web.config, I don't see any of these events triggered.
i tried catching this on the client, but this event is not tirggered:
$.connection.hub.disconnected(function () {
console.error('signalR disconnected, retrying connection');
logError('Signal lost.');
setTimeout(function () { connection.start(); }, 1000);
});
update
I also hooked into State Changed event, which does get triggered, but the re-connection attempt below does not work.
$.connection.hub.stateChanged(function (state) {
console.debug('signalR state changed', state);
if (state.newState == 1) {
console.debug('restarting');
setTimeout(function () { $.connection.hub.start(); }, 1000);
}
});
this event gets triggered twice: newState is 2 ,and then 1.
I might have a clue... Touching the Web.config produces an appPool Recycle, meaning that a new worker process will be created for new requests while the existing process will continue for a while until the remaining requests end or the timeout is reached. Request that do not end in the timeout period are terminated.
Signalr client reconnects to the new process while the long running task is running in the old process, so when on the long running task you do
GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
you actually get a reference for "old" hub while the client is connected to the "new" hub.
That's why the test preformed by Wasp worked: he was making a new request to publish on the signalr hub that was processed in the newly created worker process.
You could try to configure a singalr backplane (https://www.asp.net/signalr/overview/performance/scaleout-in-signalr), it’s really easy to configure it using Sql Server (https://www.asp.net/signalr/overview/performance/scaleout-with-sql-server). The backplane should be capable of connect the two worker processes and hopefully you will get the notification on the client.
If this is the problem, notifications generated by new requests will work even without the backplane. Notice that the real purpose of the backplane is to scale out signalr, this is, to connect a farm of WebServers between them.
Also keep in mind that running long-running task inside IIS is as task hard to achieve as, among other things, IIS does regular appPool recycles and has timeout limits for the requests to execute. I recommend that you read the following post: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
“If you think you can just write a background task yourself, it's likely you'll get it wrong. I'm not impugning your skills, I'm just saying it's subtle. Plus, why should you have to?”
Hope this helps
I want to make a service that notify the user in case there are some new messages sent to him. Thus I want to use some Comet framework that provide the server push ability. So I have looked into PokeIn.
Just wondering a thing. I have checked on the samples that they have on the website. None of them look into the database to retrieve new entries if there are some. But it is just a matter of modification to it I guess.
One of the sample implement this long polling by using a sleep on the server side. So if I use the same approach I can check the database, if there are any new entries, every 5 seconds. However this approach doesn't seem to be much different from when using polling on the client side with javascript.
This part is from a sample. As can be seen they put a sleep there for to update current time for everybody.
static void UpdateClients()
{
while (true)
{
//.. code to check database
if (CometWorker.ActiveClientCount > 0)
{
CometWorker.SendToAll(JSON.Method("UpdateTime", DateTime.Now));
}
Thread.Sleep(500);
}
}
So I wonder is this how I should implement the message notifier? It seems that the above approach is still going to push a huge load demand on the server side. The message notifier is intend to work same way as the one found Facebook.
You shouldn't implement this way, that sample is only implemented like that because the keep PokeIn related part is clear. You should implement SQL part as mentioned http://www.codeproject.com/Articles/12335/Using-SqlDependency-for-data-change-events
in order to track changes on database.
So, when you have something to send, call one of the PokeIn methods for the client side delivery. I don't know, how much your application is time critical because in addition to reverse ajax, PokeIn's internal websocket feature is very easy to activate and delivers messages to client quite fast.
You can do this with database as #Zuuum said, but I implemented it in a different way.
I'm using ASP.NET MVC with PokeIn and EF in a Windows Azure environment:
I have domain events similar to this approach: Strengthening your domain: Domain Events
When someone invokes an action, that's a Unit of Work
If that UOW succeeds then I raise a domain event (e.g. ChatMessageSent)
I have subscribers for these domain events so they can receive the event and forward the message to the PokeIn listeners
I use this pattern for all my real-time needs on my game site (making moves, actions etc in a game), I don't want to advertise it here, you can find it through me if you want.
I always use this pattern as a duplex communication solution so everybody gets their update via PokeIn, even the user who invoked the action so every client will behave the same. So when someone calls an action it won't return anything except the success signal.
The next examples are won't work because they are only snippets to demonstrate the flow
Here is an action snippet from my code:
[HttpPost]
[UnitOfWork]
[RestrictToAjax]
[ValidateAntiForgeryToken]
public JsonResult Post(SendMessageViewModel msg)
{
if (ModelState.IsValid)
{
var chatMessage = new ChatMessage
{
ContainerType = msg.ContainerType,
ContainerID = msg.ContainerID,
Message = _xssEncoder.Encode(msg.Message),
User = _profileService.CurrentUser
};
_chatRepository.AddMessage(chatMessage);
OnSuccessfulUoW = () => EventBroker.Current.Send(this, new ChatMessageSentPayload(chatMessage));
}
return Json(Constants.AjaxOk);
}
And the (simplified) EventBroker implementation:
public class UnityEventBroker : EventBroker
{
private readonly IUnityContainer _container;
public UnityEventBroker(IUnityContainer container)
{
_container = container;
}
public override void Send<TPayload>(object sender, TPayload payload)
{
var subscribers = _container.ResolveAll<IEventSubscriber<TPayload>>();
if (subscribers == null) return;
foreach (var subscriber in subscribers)
{
subscriber.Receive(sender, payload);
}
}
}
And the even more simplified subscriber:
public class ChatMessageSentSubscriber : IEventSubscriber<ChatMessageSentPayload>
{
public void Receive(object sender, ChatMessageSentPayload payload)
{
var message = payload.Message;
var content = SiteContent.Global;
var clients = Client.GetClients(c => c.ContentID == message.ContainerID && c.Content == content)
.Select(c => c.ClientID)
.ToArray();
var dto = ObjectMapper.Current.Map<ChatMessage, ChatMessageSentDto>(message);
var json = PokeIn.JSON.Method("pokein", dto);
CometWorker.SendToClients(clients, json);
}
}