Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AsyncContext response does not match original incoming request?

We have a web application with a dashboard that is continuously polling for updates. On the server-side, the request for updates is made asynchronous so that we can respond when an update occurs through a listener/notify system.

The problem we're seeing is that when one of these polling requests is being responded to, it can in some cases write to the request/response for a user-clicked link.

The incoming request for the async update looks like:

@RequestMapping("/getDashboardStatus.json")
public void getDashboardStatus(HttpServletRequest request, ...) {
    final AsyncContext asyncContext = request.startAsync();
    // 10 seconds
    asyncContext.setTimeout(10000);
    asyncContext.start(new Runnable() {
        public void run() {
            // .. (code here waits for an update to occur) ..
            sendMostRecentDashboardJSONToResponse(asyncContext.getResponse());
            if (asyncContext.getRequest().isAsyncStarted()) {
                asyncContext.complete();
            }
        }
    });
}

What's strange, is that there are links on this dashboard that go to other pages. Every ~100 clicks or so, one of them will instead of displaying the chosen page, actually display the JSON sent above!

For example, we have a separate MVC method:

@RequestMapping("/result/{resultId}")
public ModelAndView getResult(@PathVariable String resultId) {
    return new ModelAndView(...);
}

And when clicking on a link on the dashboard that visits /result/1234, every blue moon, the page will load with a 200 OK status, but instead of containing the expected HTML, actually contains the JSON for the polling request!

Is only one request per client allowed? Does a request initiated by a clicked link override any async requests that are already sitting on the server-side from the same client?

How can we manage these requests to ensure that the async response goes to the async request?

I noticed a hasOriginalRequestAndResponse() method on the AsyncContext object, but am having difficulty understanding from the Javadoc whether it's what I'm looking for.

Update: I just added a snippet like so:

String requestURI = ((HttpServletRequest)asyncContext.getRequest()).getRequestURI());
System.out.println("Responding w/ Dashboard to: " + requestURI);
sendMostRecentDashboardJSONToResponse(asyncContext.getResponse(), clientProfileKey);

And was able to reproduce the issue, during proper behavior, I see:

Responding w/ Dashboard to: /app/getDashboardStatus.json

But when I see JSON pushed to the click-initiated requests, I see:

Responding w/ Dashboard to: null
like image 269
Craig Otis Avatar asked Dec 30 '14 19:12

Craig Otis


1 Answers

I've figured this one out. The request/responses are indeed being recycled, so by hanging on to the response assigned to the AsyncContext, I was writing out to a response that was associated with a different request.

Calling startAsync() guarantees that the request/response objects will not be recycled until the asynchronous context has been completed. Despite my finding no place where the context would be completed prematurely or erroneously, it was being completed:

By the timeout.

I was able to consistently reproduce this issue by waiting for 10+ seconds with no server-side activity, allowing the JSON update request to timeout, and then clicking on a link. After the timeout, the request/response associated with the asynchronous context is timed out, and thus completed, and thus recycled.

There were two solutions that I discovered.

The first is to add an AsyncListener to the context, and keep track of whether or not the timeout had occurred. When your listener detects a timeout, you flip a boolean, and check it before writing to the response.

The second is to simply call isAsyncStarted() on the request prior to writing to the response. If the context has timed out, this method will return false. If the context is still valid/waiting, it will return true.

like image 123
Craig Otis Avatar answered Nov 15 '22 21:11

Craig Otis