Monday, October 23, 2006

Load Testing Error ASP.NET

This is going to be a boring blog post, but since I had the hardest time trying to figure out a somewhat usable solution to this problem, I figured I would share the wealth and see if anyone has a better way of handling this problem.

Background:
This is an ASP.NET 1.1 e-commerce web app written in VB.NET with SQL2000 as the backend DB. The architecture of the site consists of most of the reusable data being stored in the ASP.NET cache that expires each night at midnight and is recompiled/cached at that time or on request of a developer.

There is a user control that is utilized twice on a particular page that calls this cache for two separate datatables stored within the cache. The page will get the cached datatable, assign it to a new instance of a DataView, assign a row filter to that view, and then run through the data in a For..Each loop.

This approach works great under a normal load of the site. However, as soon as I throw the code onto a test server and then run Microsoft's Application Center Test with a load of 200 simulated users on this page, with each user requesting the page three times during the session, it starts to throw application errors. And a very specific one.

System.Web.HttpUnhandledException: Exception of type System.Web.HttpUnhandledException was thrown. ---> System.IndexOutOfRangeException: There is no row at position 4.
at System.Data.DataView.GetRecord(Int32 recordIndex)
at System.Data.DataView.IsOriginalVersion(Int32 index)
at System.Data.DataRowView.get_Item(String property)

The code that causes this error is:
objSingleShipMethod.Item("ShipCode")

in the following summarized snippet of code.

    1  For Each objSingleShipMethod In objDV

    2 

    3             Dim dtEstDate As Date

    4             dtEstDate = estDate.EstimatedDeliveryDate(objHPProductInfo.ProductType, objHPProductInfo.ProductFormat, objSingleShipMethod.Item("ShipCode"), False)

    5         Next



objSingleShipMethod is a DataRowView and objDV is the instance of the DataView from the cache that I collected for use in this function.

Like I said, this works flawlessly under a light or moderate load on the server and will not throw an exception. So, what I ended up doing, which worked for a while is the following:

Each time the particular cached objects that I need for this page is called, I set an application variable that "locks" the Dataview until the process utilizing it is completed. When it is completed, the application variable is unlocked and the next thread can then utilize the DataView. The only problem with this, is I had to set the checking of the state of the application variable to essentially "time-out" after a given period so that thread wouldn't be waiting for eternity. So, under a somewhat heavy load, this code will execute and restrict the thread from grabbing the same DataView that is being used.

So, that worked up until a few days ago. Now, I added some more code to the control that makes the page execute a little slower and this error started to happen once again. The solution I found that finally worked (which I am open to criticism and re-working) is:

    1  'Set the application lock on the Shipping View

    2         setApplicationLock("ShippingViewLocked")

    3         Dim newDV As New DataTable

    4 

    5         Dim objDV As DataView

    6         If IsNothing(HttpContext.Current.Cache("DVProductShippingMethod")) Then

    7             objDV = New DataView(Me.GetTable("ProductShippingMethod"))

    8 

    9             HttpContext.Current.Cache.Insert("DVProductShippingMethod", objDV, Nothing, GetExpirationDate, TimeSpan.Zero)

   10 

   11             If Trim(rowFilter) <> "" Then

   12                 objDV.RowFilter = rowFilter

   13                 newDV = objDV.Table.Clone

   14                 For Each dvRow As DataRowView In objDV

   15                     newDV.ImportRow(dvRow.Row)

   16                 Next

   17 

   18             End If

   19         Else

   20             objDV = CType(HttpContext.Current.Cache.Get("DVProductShippingMethod"), DataView)

   21 

   22             If Trim(rowFilter) <> "" Then

   23                 objDV.RowFilter = rowFilter

   24                 newDV = objDV.Table.Clone

   25                 For Each dvRow As DataRowView In objDV

   26                     newDV.NewRow()

   27                     newDV.ImportRow(dvRow.Row)

   28                 Next

   29             End If

   30         End If

   31 

   32         Return newDV.DefaultView



So, basically what I added to the call to get that view is to create a new "temporary" dataview, determine if the cache is present, if it is, apply the row filter to the cached dataview. Then clone the cached dataview into the temporary dataview (cloning only takes the structure, not the underlying data), loop through the cached dataview and import each resulting row into the "temporary" dataview. Then, return the temporary dataview to the page requesting it.

What was happening, as far as I can tell, is by passing the other dataview, it's not creating a new instance each time during the page execution and is somehow still grabbing ahold of the dataview located within the cache, and when you try to apply a row filter or really do anything with it, it starts to error out if commands come in simultaneously. The goal of this function was to return a brand new object with the data in it that is not tied to the cache or any other commands that could cause this error. The only way I could find to do that was to create a new view and add the columns that way. It seems crazy and weird, but it's what eventually worked.

Sorry this was so boring. I figured I needed to write it down and make it available on the internet just in case someone else has encountered the same problem and either can get some help from this or tell me I'm an idiot and should have approached it this way instead.

Monday, October 16, 2006

TiVo + FaceBook

So, it appears that I have taken the plunge and ordered a TiVo. Now, don't get excited. I didn't get the Series 3...yet. I got the Series 2 DT 180-hour one for our bedroom. When we had Dish Network in College Station we were able to watch TV until we got tired, hit pause, and watch the same show in our bedroom since it was accessing the same box. We, apparently, really miss that. So, to get us back on that trend, this TiVo will be in our bedroom and I'll keep the TimeWarner crap-tastic HD-DVR on our main TV downstairs until the Series 3 a) comes down in price and b) includes multi-room viewing. I'd really like to see the Series 3 eventually be able to push the recordings to our Series 2DT TiVo upstairs and down-convert the HD into SD (or something) so we can share the recording space.

I don't think that will be completely possible, but it's fun to dream, right?

This concludes another boring and useless update to my blog. And now Facebook users can see it too! Oh boy!