Thursday, March 04, 2004

Painfully learned lessons

or, for this, I wrote my car off.

As I noted a while back, for the last few months I've been busy on a project that's actually intended to be released. It was getting home from another Saturday at work (the 13th consecutive working day for all those trisdekaphobes out there) when I skidded in the sudden snow and slush and bent the car gently against a telegraph pole. The obvious damage was confined to the headlight, wing and wheel, but - given that it was over 10 years old, and a rear-ending back in '96 - just after the 3 year old mark - came close to being a write-off - I wasn't surprised when the garage told me it was a total loss.

What I'd been working on was a report of a memory leak detected in log-on stress tests. And there were a couple of obvious ones - stateless objects in .asp files that weren't being nulled, and would be better as globals anyway.

Next round, running the most significant COM object directly, I learned that CComBSTR is the invention of the very Devil. If you expect it to behave like std::string or even CString, you'll almost certainly leak memory. Better to write your own wrapper and count all the SysAllocStrings out and all the SysFreeStrings in again, with SysReallocStrings and any widening of data to append done under your own control. Run the direct test harness again - after starting transients, from 2000 uses to 25000 uses, memory and handles steady as a rock.

So I ran the program I'd coded using WinInet to drive a dumb web client, coded to match the test harness script (that used some test software I didn't have) to drive scripts plus COM objects. Steady as she goes. Hand over to test.

Leaking like a sieve, they say. At this point I went home, tired, perplexed and annoyed, to have the unexpected detour into a ditch.

Next lesson, Monday morning. My test harness was looping forever doing create an HINTERNET, create a session, use a number of HTTP requests, tear it all down, and repeat; theirs was using scripting to do the same set of GETs and POSTs. WTF!? I thought. Then I looked at a network trace.

If you use Connection: Keep-Alive in your requests, even if you destroy every HINTERNET and create then all anew, starting with a whole new Internet Session, the same TCP connection is held underfoot. (I'd probably have had to unload and re-load wininet.dll to force a connection close). So it was all within one ASP session, even though I was running what I thought were many. So remove the for(;;) and loop in a script. Suddenly the leak shows.

The guy who wrote the scripts looks over my shoulder at this point and asks "why are you calling this page with that argument?" - so I show him the test script. "Are you sure?" he says. So I do the obvious - capture a network trace with a real browser exercising the real server. And find that the script does indeed depart from what a browser does - it misses out the final crucially important GET-after-redirect to the page that winds up the session.

Adapt my test harness to match reality. Run it again. Steady as a rock. At this point it is probably just as well that the test team are 70 miles away, as I swear if one of them had been in the room, I would have bludgeoned them to death with my chair. But as the Ash Wednesday Chopping Block says, there are, alas, things that one can't validly give up for Lent.

No comments :