I have a ReST service I'm connecting to with a Jersey async client. I'm using InvocationCallback to handle responses as callbacks. All is well unless I bounce the server during heavy load from my client service. After a couple of bounces, I find that, beneath the covers, jersey client has registered internal callback handlers for messages that the server will never respond to (because it's been restarted and has lost any indication of an outstanding request).
I would have expected Jersey to (internally) poll its registered async handler list looking for "stale" handlers and cleaning them up with timeout exceptions to the registered invocation callback handlers periodically, but these unanswered handlers never get cleaned up.
What am I doing wrong? This has GOT to be a known issue in Jersey.
I'm building my client like this:
ClientBuilder builder = ClientBuilder.newBuilder();
this.client = builder
.executorService(es)
.register(JsonProcessingFeature.class)
.register(LoggingFeature.class)
.property(LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_CLIENT, log.getName())
.connectTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.withConfig(new ClientConfig().connectorProvider(new JdkConnectorProvider()))
.build();
Related
I am using SignalR in a Blazor server-side app. I added the Microsoft.AspNetCore.SignalR.Client Nuget package (v5.0.11) to the project, and used the following code to create the hub connection...
HubConnection hubConnection = new HubConnectionBuilder()
.WithUrl("url")
.Build();
await hubConnection.StartAsync();
I then send out a message with the following code (Debug.WriteLine added to confirm what's going on)...
Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} SignalR msg sent");
await hubConnection.SendAsync("Send", "Hello");
The component that is to handle such messages creates the hub connection and hooks up to the On handler as follows...
HubConnection hubConnection = new HubConnectionBuilder()
.WithUrl("url")
.Build();
hubConnection.On<string>("Receive", msg =>
Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} SignalR msg received - {msg}"));
await hubConnection.StartAsync();
When the message is sent out, it is definitely only being sent once (which I can confirm from the output panel, where I only see the "sent" output once), but it is received twice.
Searching around, it seems that a common reason for this is if jQuery was loaded twice. However, I have checked this, and it's not the case. It's only being loaded the once. Furthermore, one of the other developers on the team tried it, and he got the message received 15 times! Even if we had accidentally included jQuery twice, we certainly didn't include it 15 times. Also, he's using exactly the same code as me (checked out from source control), so we should get the same results if this were the issue.
Anyone any idea why this could be happening? Thanks
This can occur when you initialise a HubConnection within a component and don’t configure IDispose or IAsyncDispose on your component to dispose the HubConnection.
In a Blazor Server-Side application there is already a SignalR host pushing updates to the client.
If you want to push updates from a page you can use a Singleton Service to handle the communication.
If you set up your own SignalR hub and use the SignalR client in your components, your application is something like the diagram below:
As you can see, the client is actually running on the server. The SignalR hub you've added adds processing and memory overhead and does not add any value.
I created a simple sample app that uses a service that clients listen to for updates:
https://github.com/conficient/BlazorServerWithSignalR
Well, this is an exceptional behavior, one needed for our use-case.
We are using handler interceptor to "increment active request count" at "preHandle" method. On "afterCompletion" method we decrement the active request counter. So far so good.
Sync calls are straight forward. But in case of async there is a 2nd request (DispatcherType: ASYNC) which is being used to decrement the counter while the main request (DispatcherType: REQUEST) is used to increment the counter. We check the dispatcher type to avoid double increment.So far so good.
The problem happening in case of some problematic clients, which disconnects after firing the request. In such a case while the main request enters the server but before the async thread starts the client disconnect itself (like close the browser).In this case the 2nd request (DispatcherType: ASYNC) not getting created at all.
This situation leaving the counter increased (by the main request).
For our use-case, we have to decrement the counter no matter what if it is incremented for a request.
Looking forward to your help/suggestion here. Thanks in advance.
Other details:
Spring Boot application
Spring framework: 4.3.4.RELEASE
Tomcat : 7
Using RestController
Handler Interceptor: AsyncHandlerInterceptor
In the asynchronous mode we use ResponseBodyEmitter to send data to client
Log in server:
Exception in thread "pool-87-thread-1" java.lang.IllegalStateException: The request associated with the **AsyncContext has already completed processing**.
at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:497)
at org.apache.catalina.core.AsyncContextImpl.getRequest(AsyncContextImpl.java:209)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:198)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:170)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:164)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:123)
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:353)
at org.springframework.web.context.request.async.WebAsyncManager.access$200(WebAsyncManager.java:58)
at org.springframework.web.context.request.async.WebAsyncManager$7.handleResult(WebAsyncManager.java:416)
at org.springframework.web.context.request.async.DeferredResult.setResultInternal(DeferredResult.java:199)
at org.springframework.web.context.request.async.DeferredResult.setErrorResult(DeferredResult.java:214)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.completeWithError(ResponseBodyEmitterReturnValueHandler.java:219)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.completeWithError(ResponseBodyEmitter.java:204)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.sendInternal(ResponseBodyEmitter.java:169)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:159)
at net.atpco.pipeline.common.post.KryoResponseEmitterPostBox.send(KryoResponseEmitterPostBox.java:48)
at net.atpco.pipeline.common.post.KryoResponseEmitterPostBox.lambda$0(KryoResponseEmitterPostBox.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Updates:
On further research I found this in spring documentation... "Note that HandlerInterceptor implementations may need to do work when an async request times out or completes with a network error. For such cases the Servlet container does not dispatch and therefore the postHandle and afterCompletion methods will not be invoked. Instead, interceptors can register to track an asynchronous request through the registerCallbackInterceptor and registerDeferredResultInterceptor methods on WebAsyncManager. This can be done proactively on every request from preHandle regardless of whether async request processing will start." This deferredResultInterceptor seems like solving the issue.
I have web app on Azure with 2 slots.
Whenever the slot swap happens, all SignalR clients are disconnected and not even notified about the connection loss.
SignalR events such a Close, Error, Reconnected are never fired on the client.
How to prevent this or at least know when disconnect happens? (of course I need to avoid polling)
How to prevent this or at least know when disconnect happens?
We could enable SignalR tracing to view diagnositc infomration about events in your SignalR application. How to enable and configure tracing for SignalR servers and clients, we could refer to this document.
Detecting the reason for a disconnection
SignalR 2.1 adds an overload to the server OnDisconnect event that indicates if the client deliberately disconnected rather than timing out. The StopCalled parameter is true if the client explicitly closed the connection. In JavaScript, if a server error led the client to disconnect, the error information will be passed to the client as $.connection.hub.lastError.
C# server code: stopCalled parameter
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
if (stopCalled)
{
Console.WriteLine(String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId));
}
else
{
Console.WriteLine(String.Format("Client {0} timed out .", Context.ConnectionId));
}
return base.OnDisconnected(stopCalled);
}
JavaScript client code: accessing lastError in the disconnect event.
$.connection.hub.disconnected(function () {
if ($.connection.hub.lastError)
{ alert("Disconnected. Reason: " + $.connection.hub.lastError.message); }
});
More details we could refer to Detecting the reason for a disconnection.
How to prevent this?
We could continuously reconnect it.
In some applications you might want to automatically re-establish a connection after it has been lost and the attempt to reconnect has timed out. To do that, you can call the Start method from your Closed event handler (disconnected event handler on JavaScript clients). You might want to wait a period of time before calling Start in order to avoid doing this too frequently when the server or the physical connection are unavailable. The following code sample is for a JavaScript client using the generated proxy.
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
More details we could refer to How to continuously reconnect
I am trying to use a readonly ASP.NET session as shown by David Fowler in this gist: https://gist.github.com/davidfowl/4692934
But does this also apply when using WebSockets as the transport> I am running IIS (Express) 8.0.
What I see is that the hub method is called before the Application_BeginRequest method is called or Application_BeginRequest is not called at all. (I am using SignalR version 1.1.0)
Which Application events are called when a SignalR request is received?
Begin request should be called once during the websocket handshake and never called again after that. Hub invocations do not imply an http request is being made.
We are building an app which will send messages to the browser using SignalR. The user may have multiple browser instances open and we would like each message to be sent to the appropriate browser. Our understanding is that the ClientId ConnectionId would allow us to do this. The issue we're running into is accessing the ClientId ConnectionId, or SessionId, at the appropriate times in the codebase. Here's our scenario:
A MVC Action executes and, as part of that processing, a call to a Biztalk endpoint is made. The Biztalk execution is out of process (from the point of view of the MVC Action) and doesn't return when completed. This is by design. To notify the MVC application that it has completed, Biztalk sends a message to the MVC application's SignalR hub by calling the /myapp/signalr endpoint. The message is received by SignalR and then should be routed to the appropriate browser instance.
Since the message to SignalR is being sent by Biztalk, and not the MVC application, the ClientId of the connection to SignalR is not the one that identifies the browser instance that should receive the message. So what we are attempting to implement is somethign similar to the Return Address pattern by including the ClientId ConnectionId of the browser instance that initiates the Biztalk call in the message to Biztalk. When Biztalk sends its message to SignalR one of the contents is that original ClientId ConnectionId value. When SignalR processes the message from Biztalk it then can use the ClientId ConnectionId included in the message to route that message to the appropriate browser instance. (Yes we know that this won't work if the browser has been closed and re-opened and we're fine with that.)
The problem we face is that when initially sending the message to Biztalk from our MVC Action we cannot access the ClientId ConnectionId as it's only available in the Hub's Context. This is understandable since the MVC Action doesn't know which Hub context to look for.
What we have tried in it's place is to pass the SessionId through the Biztalk message and return it to SignalR. This solves the problem of including the browser instance identifier in the Biztalk message and returning it to SignalR. What it introduces is the fact that when a client connects to the Hub we cannot access the Session (and thus the SessionId) in the Hub's OnConnect method.
David Fowler posted a gist that reportedly shows how to make readonly SessionState accessible in a Hub but it doesn't work. (https://gist.github.com/davidfowl/4692934) As soon as we add this code into our application messages sent to SignalR cause a HTTP 500 error which is caused by SignalR throwing the following exception.
[ArgumentNullException: Value cannot be null.Parameter name: s]
System.IO.StringReader..ctor(String s) +10688601
Microsoft.AspNet.SignalR.Json.JsonNetSerializer.Parse(String json, Type targetType) +77
Microsoft.AspNet.SignalR.Json.JsonSerializerExtensions.Parse(IJsonSerializer serializer, String json) +184
Microsoft.AspNet.SignalR.Hubs.HubRequestParser.Parse(String data) +101
Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(IRequest request, String connectionId, String data) +143
Microsoft.AspNet.SignalR.<>c__DisplayClassc.<ProcessRequest>b__7() +96
Microsoft.AspNet.SignalR.<>c__DisplayClass3c.<FromMethod>b__3b() +41
Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(Func`1 func) +67
No matter the mode that we set SessionStateBehavior (as shown by David Fowler's gist) we either get this exception when sending a message to the Hub or SessionState is null when we are in the Hub's OnConnect.
So, after all that pre-amble, what we are asking is how do people update the appropriate client when working with this type of disconnected messaging in SignalR?
If you're looking to send data to clients outside of a normal request to a hub then I'd recommend having a static Concurrent Dictionary on your hub that manages your users and maps them to corresponding connection Id's.
With this approach you can send to any user at any point based on their mapped Connection Id. Therefore when sending your data to Biztalk all you need to do is send your user id (created by you) and then when the data flows back to SignalR you can lookup the ConnectionId (if one exists) for that given user id.
Lastly, you can manage your user mappings by adding users to your concurrent dictionary in OnConnected, adding only if they are not there in OnReconnected, and removing in OnDisconnected.