ASP.NET MVC 4: Only allow one request at a time
Asked Answered
L

3

8

In my ASP.NET MVC Application, I want to handle all requests sequentially; no action/controller code should be executed concurrently with another. If two requests come in at similar times, it should run the first one first, then the second one when the first one is done.

Is there a better way of doing this besides using a global lock variable?

EDIT: The application is more of a batch/service over the web that performs web service calls and cleans up a database. Different URLS in the site lead to different batch operations. This is not a site for end-users. Thus, I need to make it so that only one request to a URL (which will do some batch operations) will be done at a time, otherwise the batch operation could be corrupted if code for it runs concurrently with itself, or other batch operations. In fact, if another request comes when one is currently executing, it should not be run at all, even after the previous one finishes; it should just give an error message.

I would like to know if there was a way to do this in IIS instead of code. If I have a global lock variable, it would make the code more complicated, and I might run in a deadlock where the lock variable is set to true but never can be set to false.

EDIT: Sample code of implementation plan

[HttpPost]
public ActionResult Batch1()
{
    //Config.Lock is a global static boolean variable
    if(Config.Lock) { Response.Write("Error: another batch process is running"); return View(); }
    Config.Lock = true; 

    //Run some batch calls and web services (this code cannot be interleaved with ExecuteBatchCode2() or itself)
    ExecuteBatchCode();

    Config.Lock = false;
    return View();
}

[HttpPost]
public ActionResult Batch2()
{
    if(Config.Lock) { Response.Write("Error: another batch process is running"); return View(); }
    Config.Lock = true;

    //Run some batch calls and web services (this code cannot be interleaved with ExecuteBatchCode1() or itself)
    ExecuteBatchCode2();

    Config.Lock = false;
    return View();
}

Would I need to be worried about a case where the code does not reach Config.Lock = false, resulting in Config.Lock = true forever, causing no more requests to be served?

Lavine answered 20/6, 2014 at 15:18 Comment(1)
How about showing us what you have so far?Hutner
N
5

You could write an attribute:

public class ExclusiveActionAttribute : ActionFilterAttribute
{
    private static int isExecuting = 0;
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (Interlocked.CompareExchange(ref isExecuting, 1, 0) == 0)
        {
            base.OnActionExecuting(filterContext);   
            return; 
        }
        filterContext.Result = 
            new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        base.OnResultExecuted(filterContext);
        Interlocked.Exchange(ref isExecuting, 0);
    }
}

then use it on the controllers/methods that you want to control:

[ExclusiveAction] //either here, for every action in the controller
public class MyController : Controller
{
    [ExclusiveAction] //or here for specific methods
    public ActionResult DoTheThing()
    {
        //foo
        return SomeActionResult();
    }
}
Nowlin answered 20/6, 2014 at 16:3 Comment(4)
Before I accept this answer, is there any way that this code could result in a "deadlock" where isExecuting = 1 and can't be set to 0, considering all possible cases like exceptions, network failures, power outages, program crashing, etc?Lavine
@Lavine : I don't know for sure. I suggest a bit of testing before going live with this.Nowlin
@Lavine : #24334331Nowlin
Ended up using this approach, but with try/catch blocks to handle exceptions and set the lock value back to 0.Lavine
C
5

You have accept request as much as you can, people don't like waiting in front of browser. But after, on serve side, yuo can push them into (say) Queue<T> and process them in sequence.

In short:

  • accept in async way
  • process, on the server, in sequence
Commove answered 20/6, 2014 at 15:20 Comment(0)
N
5

You could write an attribute:

public class ExclusiveActionAttribute : ActionFilterAttribute
{
    private static int isExecuting = 0;
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (Interlocked.CompareExchange(ref isExecuting, 1, 0) == 0)
        {
            base.OnActionExecuting(filterContext);   
            return; 
        }
        filterContext.Result = 
            new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        base.OnResultExecuted(filterContext);
        Interlocked.Exchange(ref isExecuting, 0);
    }
}

then use it on the controllers/methods that you want to control:

[ExclusiveAction] //either here, for every action in the controller
public class MyController : Controller
{
    [ExclusiveAction] //or here for specific methods
    public ActionResult DoTheThing()
    {
        //foo
        return SomeActionResult();
    }
}
Nowlin answered 20/6, 2014 at 16:3 Comment(4)
Before I accept this answer, is there any way that this code could result in a "deadlock" where isExecuting = 1 and can't be set to 0, considering all possible cases like exceptions, network failures, power outages, program crashing, etc?Lavine
@Lavine : I don't know for sure. I suggest a bit of testing before going live with this.Nowlin
@Lavine : #24334331Nowlin
Ended up using this approach, but with try/catch blocks to handle exceptions and set the lock value back to 0.Lavine
D
0

the above code does not work probably because when request 1 is running and send request 2, app return service unavailable, it's good but if request 1 doesn't completed and again send request 2 to app, app running both request at same time. I'm reviewed code and change it.

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class ExclusiveActionAttribute : ActionFilterAttribute
    {
        private static int _isExecuting = 0;
        private static int _isDuplicated = 0;
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (Interlocked.CompareExchange(ref _isExecuting, 1, 0) == 0)
            {
                base.OnActionExecuting(filterContext);
                return;
            }

            Interlocked.Exchange(ref _isDuplicated, 1);
            filterContext.Result = new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);
            if (_isDuplicated == 1)
            {
                Interlocked.Exchange(ref _isDuplicated, 0);
                return;
            }
            Interlocked.Exchange(ref _isExecuting, 0);
        }
Decoder answered 5/1, 2022 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.