Monday, July 5, 2010

Leak due to XPCOM service held in global variable

I've been running some xpcshell tests. I'm testing the bittorrent tracker code, which is asynchronous so that's kinda fun. It means that I call do_test_pending(); before starting the tracker request. Oh, let's show a test:

function run_test() {
// Just check we're running tests okay
do_check_true(true);
// Check the interface has been loaded correctly
do_check_true("btIBittorrentTracker" in Components.interfaces);
// Check the component has been registered correctly
do_check_true("@wikiscraps.com/tracker;1" in Components.classes);
// Grab the service
var trackerService = Components.
classes["@wikiscraps.com/tracker;1"].
getService(Components.interfaces.btIBittorrentTracker);
do_test_pending();
// Make a request to the tracker
trackerService.start(
// infohash of http://www.mininova.org/det/3194273
"\x72\xc2\x4e\x55\xc3\x2c\xde\xf7\x23\x78\x12\xf1\x69\x4d\xad\xd1\xe7\x13\x19\x97",
// announceURI
"http://tracker.mininova.org/announce",
"", // tracker Id
// peer id (should've generated & recorded this)
"-YY0000-000003030303",
6881, // local bittorrent port
-1, // maximumPeers (-1 means use default)
0, // uploaded bytes
0, // downloaded bytes
0, // remaining bytes...
{
onResponse : function(peers, interval, trackerId) {
// got a list of peers
do_check_true(peers instanceof Array);
// finished tests
do_test_finished();
},
onError : function(serverResponse) {
// darn. Fail the test.
do_throw("Unknown error, server response was:\n" + serverResponse);
do_test_finished();
}
});
}

So you pass the tracker service the information needed to make a call to the tracker, and some kind of observer/callback to use when there's a result known. Okay? Got it? Cool.
Anyways, I started seeing a BloatView report after running this test. It didn't appear originally, so I assume it only appears when some kind of leak occurs.

== BloatView: ALL (cumulative) LEAK STATISTICS

|<----------------Class--------------->|<-----Bytes------>|<----------------Objects---------------->|<--------------References-------------->|
Per-Inst Leaked Total Rem Mean StdDev Total Rem Mean StdDev
0 TOTAL 27 520 24783 11 ( 183.34 +/- 238.02) 80546 13 ( 263.14 +/- 337.35)
2 BackstagePass 24 24 1 1 ( 1.00 +/- 0.00) 6366 4 ( 148.79 +/- 78.75)
19 XPCNativeScriptableShared 112 112 1053 1 ( 12.21 +/- 1.38) 0 0 ( 0.00 +/- 0.00)
22 XPCWrappedNative 56 168 1322 3 ( 661.75 +/- 381.27) 8044 3 ( 682.46 +/- 383.57)
23 XPCWrappedNativeProto 32 64 519 2 ( 260.00 +/- 149.61) 0 0 ( 0.00 +/- 0.00)
100 nsJSID 36 36 116 1 ( 58.25 +/- 33.42) 499 1 ( 117.51 +/- 67.79)
135 nsStringBuffer 8 8 2600 1 ( 435.41 +/- 266.14) 4832 1 ( 413.00 +/- 254.37)
144 nsSystemPrincipal 36 36 1 1 ( 1.00 +/- 0.00) 347 1 ( 4.72 +/- 1.09)
147 nsThread 72 72 3 1 ( 1.80 +/- 0.84) 4598 3 ( 571.22 +/- 379.54)

nsTraceRefcntImpl::DumpStatistics: 180 entries

Ouch. So, going back over what I'd done recently, I found this:

if (Tracker.prototype._bencoding == false) {
try {
Tracker.prototype._bencoding = Components.
classes["@wikiscraps.com/encoding/bencoding;1"].
getService(Components.interfaces.Bencoding);
} catch(e) {
callback.onError("Bencoding component not loaded");
return;
}
}

It seems loading a XPCOM service, and then leaving it in a 'global' variable and never explicitly releasing it caused a leak. Whoops. The BloatView goes away if I switch to using local variables and acquire the service each time it's needed, so it seems I can call that fixed?

More information on testing a JavaScript Add-on with XPCShell (Interfaces and components!).
More information on writing XPCShell unit tests.

No comments:

Post a Comment