1

I have come across this thread already, but I might need something else for my situation.

I have an action that returns a ViewResult, which is called by the client's $.post()

JavaScript:

var link = 'GetFoo?fooBar=' + fooBar;

var jqxhr = $.post(link, function (response) {
    $('#myDiv').replaceWith(response);
});

Controller:

public ViewResult GetFoo(String fooBar)
{
    if (Request.IsAjaxRequest())
    {
        // perform a ridiculously long task (~12 minutes)           
        // algorithm: 1) download files from the Azure blob storage
        // 2) update each file
        // 3) reupload to blob storage
        // 4) return a list of URIs to be displayed to the UI
        return View("MyFooView", data);
    }
    throw new InvalidOperationException();
}

As the comment implies, there is long task running inside the Controller. (This is a document generation module that uploads PDFs to the Azure blob storage and returns a link to it to the View.)

This is working fine in my dev machine but when it goes live in a (secure) Azure production environment, it times out. I have put in lots of logging entries everywhere and as it turns out, it is able to upload the documents and return to the controller (i.e. it reaches the controller return statement above). However, when it is time to return the model data to the View, the client script doesn't called back (i.e. the div content doesn't get replaced with the results).

Is there a way to somehow prolong the timeout of the call? It is difficult to reproduce in my (unsecure) local environment so a definitive fix will help.

If I use the attribute [AsyncTimeout(3600)] on my GetFoo() method, then this action never gets called from the UI.

Any suggestions will be appreciated.

Community
  • 1
  • 1
Alex R.
  • 4,664
  • 4
  • 30
  • 40

3 Answers3

3

The problem is that the Azure load balancer has it's own timeout which is set to one minute. Any request that takes longer than a minute gets terminated. There is no way to change this.

The way around this in the Azure environment is to have one ajax call start the process and return some sort of process ID then have the client poll another ajax call to passing in this process ID to see if it's complete. It might looks something like this uncompiled and untested code. In javascript:

var link = 'BeginFooProcessing?fooBar=' + fooBar;

var jqxhr = $.post(link, function (response) {
    var finishedlink = 'CheckFooFinished?fooId=' + response;

    // Check to see if we're finished in 1 second
    setTimeout("CheckIfFinishedYet('" + finishedlink + "')", 1000);
});

function CheckIfFinishedYet(finishedlink) {
    var response = $.post(finishedlink, function (response) {
        if (response == null) {
            // if we didn't get a result, then check in another second
            setTimeout("CheckIfFinishedYet('" + finishedlink + "')", 1000);
        }
        else {
            // Yay, we've got a result so we'll just write it out
            $('#myDiv').replaceWith(response);
        }
    });
}

And in your controller:

public ViewResult BeginFooProcessing(String fooBar)
{
    if (Request.IsAjaxRequest())
    {
        Guid fooId = Guid.NewGuid();

        var result = new FooResult
                        {
                            FooId = fooId,
                            HasFinishedProcessing = false,
                            Uris = new List<string>()
                        };

        // This needs to go to persistent storage somewhere
        // as subsequent requests may not come back to this
        // webserver
        result.SaveToADatabaseSomewhere();

        System.Threading.Tasks.Task.Factory.StartNew(() => ProcessFoo(fooId));


        return View("MyFooStartView", fooId);
    }
    throw new InvalidOperationException();
}

private void ProcessFoo(Guid fooId)
{
    // Perform your long running task here

    FooResult result = GetFooResultFromDataBase(fooId);

    result.HasFinishedProcessing = true;
    result.Uris = uriListThatWasCalculatedAbove;

    result.SaveToADatabaseSomewhere();
}

public ViewResult CheckFooFinished(Guid fooId)
{
    if (Request.IsAjaxRequest())
    {
        FooResult result = GetFooResultFromDataBase(fooId);

        if (result.HasFinishedProcessing)
        {
        // Clean up after ourselves
        result.DeleteFromDatabase();

            return View("MyFooFinishedView", result.Uris);
        }

        return View("MyFooFinishedView", null);

    }
    throw new InvalidOperationException();
}

private class FooResult
{
    public Guid FooId { get; set; }

    public bool HasFinishedProcessing { get; set; }

    public List<string> Uris;
}

Hopefully that will give you a starting point.

Community
  • 1
  • 1
knightpfhor
  • 9,299
  • 3
  • 29
  • 42
  • That's interesting. I'll try that if the `Server.ScriptTimeout` will not work. Thanks. – Alex R. Jun 01 '11 at 01:34
  • Do you have an example of this ajax-polling-passing-id technique? I'm trying to figure out how it will work in my case. – Alex R. Jun 02 '11 at 08:48
  • OK, I've added a fairly extensive example. It won't run as is, but it should be enough to give you a starting point. – knightpfhor Jun 02 '11 at 20:54
  • 1
    don't forget destroy the javascript timer once the result is obtained. – vtortola Jun 02 '11 at 21:01
  • 1
    Thanks, it worked! I just managed to change a few: I separated the `CheckFooFinished` with the actual generation of the view. Thus, the `CheckFooFinished` method only returns a boolean (if the process has finished) or an error message, via JSON. Then another method from the client does the `post` to retrieve the `ViewResult`. (I also cleared the javascript timeout as well as @vtortola suggested.) As an added bonus, my "ridiculously" long task trimmed down from 12 to 2 minutes! – Alex R. Jun 03 '11 at 07:41
2

you want to look at this

answer to your question is: [AsyncTimeout(3600000)]

see more here Controller Timeout MVC 3.0

Community
  • 1
  • 1
cpoDesign
  • 8,953
  • 13
  • 62
  • 106
  • If I use `[AsyncTimeout(3600000)]` attribute on the controller method, the action never gets called from the jQuery `$.post()`. – Alex R. May 31 '11 at 09:06
  • that sould not be problem, still if yes, you can use this where you set the timeout:>article:http://codebetter.com/petervanooijen/2006/06/15/timeout-of-an-asp-net-page/ also make sure that db does not hit timeout too – cpoDesign May 31 '11 at 09:32
  • @cpoDesign, I'll give `Server.ScriptTimeout = 3600;` a try like in your link and let you know. It's similar to the thread I mentioned above. – Alex R. May 31 '11 at 10:00
  • yep, this is how i am using it – cpoDesign May 31 '11 at 12:58
  • @cpoDesign, My controller calls are not asynchronous, that's why somehow this solution does not work? – Alex R. Jun 02 '11 at 09:45
  • what the error you get? and what is the code you using as ajax request? can you please edit your original question? – cpoDesign Jun 02 '11 at 10:31
  • I have updated the question, but not sure if it will help. I cannot make it more simpler. Basically I am using `$.post()` to call the controller action in this case. Shall I use `$.ajax()` instead? – Alex R. Jun 02 '11 at 10:46
  • 1
    as far as i know this might be the solution for you http://www.beansoftware.com/ASP.NET-Tutorials/Long-Operations.aspx, otherwise i do not know – cpoDesign Jun 02 '11 at 11:54
1

To use Async controllers your controller has to inherit from AsyncController:

public class WebsiteController : AsyncController

And then any Action using Asynchronous methods has to use the format of

public void ActionNameAsync(int param)
public ActionResult ActionNameCompleted(int param)

Where ActionName is the name of your action, and instead of the Async function use

AsyncManager.OutstandingOperations.Increment();

each time you start a new aysnchronous method and

AsyncManager.OutstandingOperations.Decrement();

when the method finishes, after all outstanding operations have completed it'll move along to the Completed function (make sure to specify the parameters you need for the completed function in the async function so it knows what to pass along)

AsyncManager.Parameters["param"] = "my name";

Then using the AsyncTimeout attribute would actually affect the function. I'm not sure what happens if you try to apply that attribute to a action that isn't in an async controller.

These changes wouldn't require changing any of the references to the action in the javascript and what not as you'd still just request 'ActionName' and it would look to see if there was the Async/Completed setup version and if not it'd look for just that normal action and use whichever it finds.

TheKrush
  • 81
  • 1
  • 6