Using ReorderList Control, Fast Reordering Causing Issues

I am using the ReorderList control from the AJAX Control Toolkit on my fantasy cheat sheet creation site. It allows me to re-order items via AJAX requests. It appears I am running into some concurrency issues if I try to reorder records too quickly.

  1. If I reorder items slowly (say give a second between each reorder), there is never a problem.
  2. If I reorder quickly (say move a record once, then again within a second or so) only the first ordering operation is performed. Not only that, ALL FURTHER REORDERING IS IGNORED UNTIL THE PAGE IS RELOADED.

Obviously something is getting locked-up if I reorder quickly and all is lost until a page reload. It would seem to be that each reorder request (done via AJAX) would simply get queued if they come in quickly, but apparently that isn’t the case. I have looked in my exception logs and no exceptions are being generated.

One important note is that this only seems to be occurring in production on my shared hosting account. On my local machine I can go as fast as I want and never run into an issue. My first instinct it to say that it is a problem with the control, but the fact that I only see the problem in a shared hosting environment makes me thing otherwise.

The code being executed on reorder is pretty simple, I call a BLL method to reorder my items in the database:

    /// <summary>
    /// This method is fired each time the items are reordered
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void rolReorderList_ItemReorder(object sender, ReorderListItemReorderEventArgs e)
    {
      // issue the reorder call to the business layer
      if (this.User.Identity.IsAuthenticated)
      {
        // we know the id of the sheet is determined by the dropdownlist
        CheatSheet.ReorderCheatSheetItems(int.Parse(ddlAvailableSheets.SelectedValue), e.OldIndex, e.NewIndex);
        // once the user reorders once, we note that they no longer need to see the instructional message
        Profile.SiteSettings.FiguredOutReordering = true;
      }
      else
      {
        // we know the id of the sheet by what is stored in a session variable
        CheatSheet.ReorderCheatSheetItems((int)Session[MySession.CurrentSportCode + "_" + MySession.GetCurrentVisitorSheetPosition(MySession.CurrentSportCode)], e.OldIndex, e.NewIndex);
        // once the visitor reorders once, we note that they no longer need to see the instructional message
        MySession.FiguredOutReordering = true;
      }
    }

You can see the effect here. Reorder a few items slowly then reorder, reload the page, and you’ll see they’re saved. Reorder a few items very quickly, reload the page, and you’ll see that only the first request was processed.

What you are probably running into is the fact that ASP.NET AJAX is not asynchronous, even if the javascript layer tries to behave that way. Typically the killer is ViewState – it looks like you are sending 200kb with each request. Which has to get to the server, get deserialized and processed and then get returned from the server for ASP.NET to update it’s stuff. A faster server might help a bit, but there is a reason that serious, web 2.0 ajaxy stuff isn’t done using ASP.NET ajax . . .

That’s what I was afraid of. Unfortunately AJAX is built-into the ReorderList control so I’m at it’s mercy. I am moving a large arount of data but that is by design. In actual practice I can’t see many people running into this problem, but I can’t rule it out.

What I’m still a bit confused about is why one mis-handled requests breaks all further requests. As I mentioned previously, the first request that isn’t handled properly marks the death of that ‘session’ (not really the session, but the changes requested by the user until the next postback). In a sense this is good because if you miss one request, the next request is going to start throwing off the sequence order. In another sense, if only one reorder were missed in a session of 50 other reorders, then it would be nice to get 49 our of 50 right. I already have code implemented which checks each sheet’s sequence for validity and re-sequences sheets as needed.

Would there be some sort of way to lock the reorder method so that any subsequent requests are ignored until the current request has completed?

The problem really boils down to how ASP.NET ajax is faking things here – basically your page’s viewstate looks like it is out of wack to the server so subsequent requests fail on an infrastructure level before your re-ordering code can kick in. And once it is out of wack it stays out of wack.

The best solution would be to find something purely javascript and use that with a submit changes button. You might be able to come up with some sort of shared Session-wide lock but that would be pretty nasty overhead and also be a bear to test.

I guess a first step could be to greatly reduce the ViewState on a test page (probably by removing the vast majority of sheet features) to see if this alleviates the issue. If ViewState is found to be the culprit, then I could try to whittle it down as much as possible (pretty sure I’ve already done this), for nothing else than to reduce the chance for error. Even a half-second improvement would make me sleep better.

If locking isn’t an option, would spawning a new thread for each reorder request do anything to alleviate the issue?

EDIT: Just read your reply. I guess if the page is out of whack then new threads won’t help either. I tried the ‘Submit’ solution in my initial design and it was met with lots of angry users who assumed everything was saved asynchronously. I think reducing the ViewState (if that is indeed the problem) may be my only option at this point.

On a side note, how did you determine the size of each request?

I was using either firebug’s net inspector or chrome’s inspector to peer into the requests. Fiddler is another good choice. I’m actually know to keep one of those up in a monitor while doing QA, amazing how many bugs you can find by looking for what you can’t see. Especially the sort of things that kill you in production – exceptions are expensive things.

The reason whittling down the viewstate will likely help you is that reducing the amount of stuff in transit will always help, and you get to reduce server overhead of parsing / deserializing that and then sending the same payload down the wire. I would be very leery of trying to build some locking or threading mechanisim here – ultimately the issue is it is a synchrnous app so it should be have synchronously.

Other UI hacks to consider would be to use one of those modal updating overlays to lock the UI until the update is complete.

I took your advice and fired-up FireBug to look at the request times when issuing reordering AJAX requests. I use the ‘Net’ tab in FireBug and first cleared all request listings, then I reorder an item here, and I’m only seeing 375B of data being passed to the server. Were you using a different method than this to see over 200K of data?

What I found was that, even though I’m passing the same amount of data in the AJAX request when reordering a sheet (local vs prod), the total time to receive the response was .434 seconds locally, but 6.25 seconds on the production server. If it is true that I’m only sending 375B of data in the AJAX request, then I’m not sure cutting down on request size is going to help me since that seems pretty small?

I did find that, on my football sheets, I’m actually passing a bit more data (492B) in a reorder AJAX request, but the response comes back in 1.66 seconds. This seems to hint that that I’m doing some extra processing in my racing sheets which is making the response time longer.

I got my 200k from looking at the content-length header for your POST. Just to make sure I wasn’t carrying digits wrong, I copied the contents into a file and got a 209kb file I would have attached had this forum let me. The size you are seeing listed is the response, which is a lithe 375b or so.

Figuring out what is going on in production might be tricky here if you are on a shared host – just so many variables there to cover. You can pretty easily add some trace statements to your code and then use the ASP.NET tracing features to see what is happening, presuming they don’t lock you out of running the traces which I probably would if I had a shared ASP.NET server.

Yeah this is a shared host so I’m not sure if I can run traces or not, but I’ll look into it. Thanks again.