Wednesday, July 14, 2010

Brick Wall - Memory Leak

Okay, I've reached a wall. Every time I do sockets, I end up with a memory leak.
Here's an example JS script that can be run with xpcshell (./run_mozilla.sh ./xpcshell pathToWhereYouSavedScript when you've built firefox).

The 'main' method for this program is testSockets(). It creates a server, and then connects to that server. We then see in testSockets_tick() that every 200 milliseconds we'll write more data out. We also see in the onDataAvailable() that the data is read, but not assigned to any variable. If this program is run, it chews and chews and chews memory.

I would assume that the inputstream/binaryinputstream is not holding the data, as calling readBytes() would release the data from the inputstream (and create a string, which would be soon freed by it's reference count being 0) - is this correct?

I would assume that the outputstream/binaryoutputstream is not holding the data, as it would be cleared after being sent - is this correct?

If both of the above are correct, where am I leaking?


/**************************
* This section is from mozilla/src/testing/xpcshell/head.js
* Licensed MPL 1.1/GPL 2.0/LGPL 2.1... as much as I'd like to spend half this post with a license,
* see http://hg.mozilla.org/mozilla-central/file/17dc041b9884/testing/xpcshell/head.js
*******************/
function do_timeout(delay, expr) {
var timer = Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
timer.initWithCallback(new _TimerCallback(expr, timer), delay, timer.TYPE_ONE_SHOT);
}
var _pendingCallbacks=[];
function _TimerCallback(expr, timer) {
this._expr = expr;
// Keep timer alive until it fires
_pendingCallbacks.push(timer);
}
_TimerCallback.prototype = {
_expr: "",

QueryInterface: function(iid) {
if (iid.Equals(Components.interfaces.nsITimerCallback) ||
iid.Equals(Components.interfaces.nsISupports))
return this;

throw Components.results.NS_ERROR_NO_INTERFACE;
},

notify: function(timer) {
_pendingCallbacks.splice(_pendingCallbacks.indexOf(timer), 1);
eval(this._expr);
}
};
/**************************
* Code we're testing.
*********************/

var serverInfo;
var clientInfo;
var data;
function testSockets() {
// Create the data
data = (new Array(1024 * 20)).join("a");
// Create a server
serverInfo = startSocketServer(6503,function(result) {
serverInfo = result;
print("Started");
testSockets_tick();

});
clientInfo = connectToSocketServer("127.0.0.1",6503,function() {
// okay, disconnected.
print("Disconnected");
});
}
var ticks=0;
function testSockets_tick() {
// Every tick, send more data to client
serverInfo.sout.write(data,data.length);
ticks++;
do_timeout(200, "testSockets_tick();");
}

function connectToSocketServer(host,port,callback) {
var result = { sout : false, sin : false};
var transportService =
Components.classes["@mozilla.org/network/socket-transport-service;1"].
getService(Components.interfaces.nsISocketTransportService);
var transport = transportService.createTransport(null,0,host,port,null);

var outstream = transport.openOutputStream(0,0,0);
result.sout = outstream;
var stream = transport.openInputStream(0,0,0);
var instream = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
instream.setInputStream(stream);
result.sin = instream;

var dataListener = {
data : "",
onStartRequest: function(request, context){
},
onStopRequest: function(request, context, status){
instream.close();
outstream.close();
callback();
},
onDataAvailable: function(request, context, inputStream, offset, count){
// Not even going to save the data we read, let it go.
instream.readBytes(count);
}
};
var pump = Components.
classes["@mozilla.org/network/input-stream-pump;1"].
createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(stream, -1, -1, 0, 0, false);
pump.asyncRead(dataListener,null);
return result;
}

function startSocketServer(port,readyCallback) {
// http://web.archive.org/web/20080313034101/www.xulplanet.com/tutorials/mozsdk/serverpush.php
if (port === undefined) {
port = 7055;
}
var result = {};
var listener = {
onSocketAccepted : function(serverSocket, transport) {
result.sout = transport.openOutputStream(0,0,0);
var instream = transport.openInputStream(0,0,0);
result.sin = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
result.sin.setInputStream(instream);

if (!(readyCallback === undefined)) {
readyCallback(result);
}
},
onStopListening : function(serverSocket, status){
print("Server shutdown");
}
};

result.serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
.createInstance(Components.interfaces.nsIServerSocket);
result.serverSocket.init(port,false,-1);
result.serverSocket.asyncListen(listener);
return result;
}

testSockets();

/**************
* Code to force xpcshell NOT to quit, allowing async processing
* From https://developer.mozilla.org/en/XPConnect/xpcshell/HOWTO
***************/
gScriptDone = false;
var gThreadManager = Components.classes["@mozilla.org/thread-manager;1"]
.getService(Components.interfaces.nsIThreadManager);
var mainThread = gThreadManager.currentThread;

while (!gScriptDone)
mainThread.processNextEvent(true);
while (mainThread.hasPendingEvents())
mainThread.processNextEvent(true);




Update: I've separated out the code into a script that runs the server, and a script that runs the client. By watching the process id, I can see that it's the client that is having the problem. nsIBinaryInputStream perhaps. So, I kept trying a few things, changing the method I was using. I tried calling gc(); at different times - this worked, but I was getting some strange results depending on how often I was calling it. I used if(count++>20000) { count=0; gc(); } in the method that received data; this worked well, but if I changed it to if(count++==20000) it didn't work at all... bizarre. I've now moved the gc(); call into it's own function that is called every 4 seconds and it's going okay. This is fantastic to me. This could've been what was causing my issues a year ago, and to finally be rid of it... could this be true? Hmm... well, this debug session has left me tired and drained (it's 7:45AM), but I think it's left my code in a better state than I am.

No comments:

Post a Comment