1

(I apologize for changing the question)

The following snippet is from a MVC.NET controller (.NET: v4.5; AspNet.MVC: v5.2.3) . After LongOperation is called, it:

  • Spawn a process
  • Waits for its completion
  • Monitors a few LOG files
  • Uses SignalR to notify browser of the progress from the LOG files

(I have omitted the code for simplicity)

All this works, only while LongOperation is running, no other HTTP requests are handled by the controllers.

They get handled after the LongOperation completes and the action method returns result to the AJAX call.

What am I messing up? Thank you in advance.

enter image description here

Update (for @angelsix comment): Here is a simplified setup:

  • I have removed async/await as advised
  • Added breakpoints as advised
  • Verified they are hit as explained in the above

Basically: same result, see the console.log-ed text and timestamps Will appreciate any help from the community. Thank you in advance!

GUI and log GUI and log

Action methods in the Controller

[AjaxOnly]
public ActionResult _RunLongOperation(string hubId)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(1000);
            ProgressNotifierHub.Notify(hubId, string.Format("Notification from _RunLongOperation {0}", i));
        }
        return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunLongOperation : OK");
    }
    catch (Exception)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "_RunLongOperation : NOK");
    }
}

[AjaxOnly]
public ActionResult _RunAnotherOperation(string hubId)
{
    return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunAnotherOperation : OK");
}

Razor View (partial) and javascript with SignalR hub setup Ajax calls

<script src="~/signalr/hubs"></script> 

@{ 
    Layout = null;
}

<button id="longOperationBtn" type="button" class="t-button" style='width: 155px'>Long Operation</button>
<button id="anotherOperationBtn" type="button" class="t-button" style='width: 155px'>Another Operation</button>


<script type="text/javascript">

    $(function () {
        setupEventHandlers();
        setupProgressNorificator();
    });


    function setupEventHandlers() {

        $('#longOperationBtn').click(function (event) {
            requestOperation('_RunLongOperation')
        });

        $('#anotherOperationBtn').click(function (event) {
            requestOperation('_RunAnotherOperation')
        });
    }

    function requestOperation(method) {
        trace(method + ' requested');

        $.ajax({
            url: '/Profiles/Validate/' + method,
            type: 'GET',
            data: { hubId: $.connection.hub.id },
            contentType: 'application/json; charset=utf-8',
            success: function () {
                trace(method + ' completed');
            },
            error: function () {
                trace(method + ' failed');
            }
        });
    }

    function setupProgressNorificator(profileId) {
        var hub = $.connection.progressNotifierHub;
        hub.client.notify = function (notification) {
            console.log(notification);
        };
        $.connection.hub.start();
    }

    function trace(s) {
        console.log('[' + new Date().toUTCString() + '] ' + s);
    }

</script>
  • To clarify - the long operation does return, verified. – Dragimi Smehurko Apr 06 '18 at 23:25
  • Can you explain more about, like adding the full sequence of your scenario? – ElasticCode Apr 06 '18 at 23:43
  • Sure: I initialize the SignalR.js hub on document load. Upon button click, I make an AJAX call to _RunLongOperation action method. It calls LongOperaion() method which spawns an external process, monitors log files and replies progress over SignalR hub. This works but no other HTTP requests are possible - navigating somewhere else in the browser is blocked and proceeds after the AJAX call gets result. – Dragimi Smehurko Apr 06 '18 at 23:53
  • We would need to see the source of both calls (the review one) and are you putting a breakpoint at the start of each action as they should instantly reach the action when called nothing would block that – angelsix Apr 07 '18 at 02:30
  • You didn't change the method to async your new change will force the block also – ElasticCode Apr 07 '18 at 09:51
  • 1
    This is not how MVC works. You block an action method it WILL NOT block another call. Otherwise the entire web would fall over. Imaging the entire google website servering its billions of search results one at a time? https://stackoverflow.com/questions/1763775/asp-net-mvc-controller-lifecycle?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa. As I have mentioned before, we still need more information as as your code is right now, there is no blocking issue. Even if you said Thread.Sleep(10000000) you can hit that call twice instantly no problem – angelsix Apr 07 '18 at 14:16
  • I 100% agree with you. Nevertheless, I am describing what I am seeing: after the call to an action method that has Thread.Sleep() inside, no other navigation is possible before the above action method finishes. This was the reason I asked the community. No help? – Dragimi Smehurko Apr 08 '18 at 21:15

4 Answers4

1

It looks like you are running the client "test" on Chrome. I'm not sure what the 2018 limitation is (it seems to change from release to release), but browsers do limit the number of concurrent connections allowed to the same host. I believe Chrome's limit is 6 or 8.

The log you posted appears to be from the client side. If it is, the behavior you are seeing could actually be the client waiting for a free connection to connect to the server - the problem may not be with ASP.NET at all, but rather with how you are testing.

It would be easy to validate - write another test that you can run concurrently with your current test in a separate browser window that just calls the "short" operation. If it has latency, I'm wrong. If it doesn't, well hopefully I've helped out!

Joe Enzminger
  • 11,110
  • 3
  • 50
  • 75
  • Thank you Joe, this helped. Using Chrome (for the long operation) and IE (for anything else) does not cause blocking. The remaining question is where all these connections are coming from? I have the 2 Ajax requests (the "long" and "other" one) and the SignalR poll. This is what I see in Network window of Chrome. These are 3, not 6. Any ideas ? Thanks again! – Dragimi Smehurko Apr 10 '18 at 19:06
  • I'd have to see the network tab while the test application was "blocked". – Joe Enzminger Apr 10 '18 at 19:47
0

The issue

Your response does not mention anything about caching. So I suspect the browser is caching the response and using that.

Verify

To verify press F12 in the browser to open developer tools and then see if the second response shows a status of 304 or states Cached

Solve

To prevent caching on the action, inside the action you want to do this

Prevent Caching in ASP.NET MVC for specific actions using an attribute

angelsix
  • 392
  • 1
  • 7
  • Thank you for your advice. The issue was different - I updated he question. – Dragimi Smehurko Apr 06 '18 at 23:47
  • Ok we still need more information to diagnose it. For example press F12 in browser then go to Network. Click the button and watch the call appear. You should hit your action. Click it again and what result do you get? Please add all this to the question otherwise it will be very difficult to diagnose accurately without guessing – angelsix Apr 07 '18 at 00:03
0

According to this:

Some ... protocols like HTTP Polling (XHR) use up to two simultaneous connections per ... client. It is important to understand that the maximum number of connections is per browser and not per browser tab. Attempting to run multiple clients within the same browser might cause this limit to be reached.

In my case, SignalR client was using long polling. A fried reported that when Web sockets are used, there was no blocking issue. Closing the subject. Thanks for the help Joe.

0

You need to set your controller to have read-only session state behavior. Then you can do ajax requests to the controller during running a long controller method. In other case (as the one that you are complaining about) the requests will be queued and call all at once after finishing the controller action call. Just put this before controller class definition

[SessionState(SessionStateBehavior.ReadOnly)]

Rob
  • 26,989
  • 16
  • 82
  • 98
Maci
  • 1