I am writing a node.js script that takes data from one connected socket and sends it to another connected socket. During testing I noticed that if I disconnect and reconnect a client over and over while a server is sending large amounts of data I get a memory leak. The following is the node.js code.
var net = require('net');
var logServer = net.createServer();
var clientList = [];
var clientIPList = [];
var serverList = [];
var serverIPList = [];
var port = 6451;
logServer.on('connection', function(client) {
client.setEncoding('utf8');
client.once('data', function(data) {
if (data[0].toString() == 'S') {
var server = client;
client = undefined;
serverList.push(server);
serverIPList.push(server.remoteAddress + ":" + server.remotePort);
console.log('Server connected: %s:%d', server.remoteAddress, server.remotePort);
server.on('data', function(data) {
for(var i=0;i<clientList.length;i+=1) {
try {
clientList[i].write(data);
} catch (err) {
console.log('Error writing to client "data event": ' + clientIPList[i] );
// close and null the socket on write error
try {
clientList[i] = null;
clientList[i].end();
} catch (err) {}
clientList.splice(i, 1);
clientIPList.splice(i, 1);
}
}
})
server.on('end', function() {
try {
var d;
if( (d = serverList.indexOf( server )) != -1 ) {
console.log('Server disconnecting "end event": ' + serverIPList[d]);
try {
serverList[d] = null;
serverList[d].end();
} catch (err) {}
serverList.splice(d, 1);
serverIPList.splice(d, 1);
}
else {
console.log('Server disconnecting "end event": unknown server');
}
} catch (err) {
console.log('Error cleaning up server socket list on "end event"');
}
})
server.on('timeout', function() {
try {
var d;
if( (d = serverList.indexOf( server )) != -1 ) {
console.log('Server disconnecting "timeout event": ' + serverIPList[d]);
try {
serverList[d] = null;
serverList[d].end();
} catch (err) {}
serverList.splice(d, 1);
serverIPList.splice(d, 1);
}
else {
console.log('Server disconnecting "timeout event": unknown server');
}
} catch (err) {
console.log('Error cleaning up server socket list on "timeout event"');
}
})
server.on('error', function(e) {
try {
var d;
if( (d = serverList.indexOf( server )) != -1 ) {
console.log('Server disconnecting ' + e.code + ' "error event": ' + serverIPList[d]);
try {
serverList[d] = null;
serverList[d].end();
} catch (err) {}
serverList.splice(d, 1);
serverIPList.splice(d, 1);
}
else {
console.log('Server disconnecting "error event": unknown server');
}
} catch (err) {
console.log('Error cleaning up server socket list on "error event"');
}
})
server.on('close', function() {
try {
var d;
if( (d = serverList.indexOf( server )) != -1 ) {
console.log('Server disconnecting "close event": ' + serverIPList[d]);
try {
serverList[d] = null;
serverList[d].end();
} catch (err) {}
serverList.splice(d, 1);
serverIPList.splice(d, 1);
}
} catch (err) {
console.log('Error cleaning up server socket list on "close event"');
}
})
server.on('drain', function() {
})
}
else {
clientList.push(client);
clientIPList.push(client.remoteAddress + ":" + client.remotePort);
console.log('Client connected: %s:%d',client.remoteAddress, client.remotePort);
client.on('data', function(data) {
console.log('writing "%s" to %d servers', data.replace(/[\r\n]/g,''), serverList.length);
for(var i=0;i<serverList.length;i+=1) {
try {
serverList[i].write(data);
} catch (err) {
console.log('Error writing to server "data event": ' + serverIPList[i] );
try {
serverList[i] = null;
serverList[i].end();
} catch (err) {}
serverList.splice(i, 1);
serverIPList.splice(i, 1);
}
}
})
client.on('end', function() {
try {
var d;
if( (d = clientList.indexOf( client )) != -1 ) {
console.log('Client disconnecting "end event": ' + clientIPList[d]);
// close and null the socket
try {
clientList[d] = null;
clientList[d].end();
} catch (err) {}
clientList.splice(d, 1);
clientIPList.splice(d, 1);
}
else {
console.log('Client disconnecting "end event": unknown client');
}
} catch (err) {
console.log('Error cleaning up socket client list on "end event"');
}
})
client.on('timeout', function() {
try {
client.end();
} catch (err) {
var d;
if( (d = clientList.indexOf( client )) != -1 ) {
console.log('Error closing client connection "timeout event": ' + clientIPList[d]);
}
else {
console.log('Error closing client connection "timeout event": unknown client');
}
}
try {
var d;
if( (d = clientList.indexOf( client )) != -1 ) {
console.log('Client disconnecting "timeout event": ' + clientIPList[d]);
try {
clientList[d] = null;
clientList[d].end();
} catch (err) {}
clientList.splice(d, 1);
clientIPList.splice(d, 1);
}
else {
console.log('Client disconnecting "timeout event": unknown client');
}
} catch (err) {
console.log('Error cleaning up client socket list on "timeout event"');
}
})
client.on('error', function(e) {
try {
var d;
if( (d = clientList.indexOf( client )) != -1 ) {
console.log('Client disconnecting ' + e.code + ' "error event": ' + clientIPList[d]);
try {
clientList[d] = null;
clientList[d].end();
} catch (err) {}
clientList.splice(d, 1);
clientIPList.splice(d, 1);
}
else {
console.log('Client disconnecting ' + e.code + ' "error event": unknown client');
}
} catch (err) {
console.log('Error cleaning up client socket list on "error event"');
}
})
client.on('close', function() {
try {
var d;
if( (d = clientList.indexOf( client )) != -1 ) {
console.log('Client disconnecting "close event": ' + clientIPList[d]);
try {
clientList[d] = null;
clientList[d].end();
} catch (err) {}
clientList.splice(d, 1);
clientIPList.splice(d, 1);
}
} catch (err) {
console.log('Error cleaning up client socket list on "close event"');
}
})
client.on('drain', function() {
// nothing
})
}
})
})
logServer.listen( port );
As far as I am aware, I am handing all critical 'net' events and I am cleaning up the sockets properly, once I detect a disconnect. Here are the two scripts that I am using to test. The first just connects and disconnects as a client over and over and the second sends data as a server. I run them both simultaneously.
condiscon.rb: Connects and disconnects after registering itself as a client "sends a newline once connected". I run with './condiscon.rb 1000'
#!/usr/bin/ruby
require 'rubygems'
require 'socket'
def connectFlac
host = '10.211.55.10'
port = 6451
sock = TCPSocket.open( host, port )
sock.puts( "" )
sock
end
sock = connectFlac()
data = []
user_agents = {}
instances_lat = {}
count = ARGV.shift.to_i
while( count > 0 )
sock = connectFlac()
sleep( 0.05 )
sock.close()
sleep( 0.05 )
count-= 1
end
dataflood.rb: Connects as a server and sends ~2600 byte packets of abcde with a counter. I run 'dataflood.rb 30000'
#!/usr/bin/ruby
require 'socket'
def connectFlac
host = '10.211.55.10'
port = 6451
sock = TCPSocket.open( host, port )
sock.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
sock.puts( "S" )
sock
end
def syntax()
print "./script number_of_packets\n"
exit( 1 )
end
data = ""
(1..100).each {
data+= "abcdefghijklmnopqrstuvwxyz"
}
sock = connectFlac()
numpackets = ARGV.shift.to_i || syntax()
counter = 1
byteswritten = 0
while( numpackets > 0 )
r,w,e = IO.select( nil, [sock], nil, nil )
w.each do |sock_write|
print numpackets, "\n"
sock.write( counter.to_s + "|" + data + "\n" )
sock.flush()
byteswritten+= counter.to_s.length + 1 + data.length + 1
counter+= 1
numpackets-= 1
end
end
sock.close()
print "Wrote #{byteswritten} bytes\n"
Here are some of the results that I am seeing. When running a memory profile on logserver.js before any testing it uses about 9 megabytes of resident memory. I am including a pmap to show the section of memory that the leak appears to occupy.
[root@localhost ~]# ps vwwwp 20658
PID TTY STAT TIME MAJFL TRS DRS **RSS** %MEM COMMAND
20658 pts/4 Sl+ 0:00 0 8100 581943 **8724** 0.8 /usr/local/node-v0.8.12/bin/node logserverdemo.js
[root@localhost ~]# pmap 20658
20658: /usr/local/node-v0.8.12/bin/node logserverdemo.js
0000000000400000 8104K r-x-- /usr/local/node-v0.8.12/bin/node
0000000000de9000 76K rwx-- /usr/local/node-v0.8.12/bin/node
0000000000dfc000 40K rwx-- [ anon ]
**000000001408a000 960K rwx-- [ anon ]**
0000000040622000 4K ----- [ anon ]
After running the two ruby scripts above against the logserver at the ame time here is what memory looks like about 30 minute after the traffic stops. (I waited for all gc to happen)
[root@localhost ~]# ps vwwwp 20658
PID TTY STAT TIME MAJFL TRS DRS RSS %MEM COMMAND
20658 pts/4 Sl+ 0:01 0 8100 665839 **89368** 8.7 /usr/local/node-v0.8.12/bin/node logserverdemo.js
[root@localhost ~]# pmap 20658
20658: /usr/local/node-v0.8.12/bin/node logserverdemo.js
0000000000400000 8104K r-x-- /usr/local/node-v0.8.12/bin/node
0000000000de9000 76K rwx-- /usr/local/node-v0.8.12/bin/node
0000000000dfc000 40K rwx-- [ anon ]
**000000001408a000 80760K rwx-- [ anon ]**
0000000040622000 4K ----- [ anon ]
0000000040623000 64K rwx-- [ anon ]
dataflood.rb wrote a total of 78198894 bytes of data and the leak is very close. I dumped the memory at 0x1408a000 and I saw that most of the packets I was sending from dataflood.rb were stuck in memory.
[root@localhost ~]# ./memoryprint 20658 0x1408a000 80760000 > 20658.txt
[root@localhost ~]# strings 20658.txt | grep '|abcde' | wc -l
30644
[root@localhost ~]# strings 20658.txt | grep '|abcde' | sort | uniq | wc -l
29638
after waiting 24 hours the memory still had not freed. Any help that anyone can give me would be greatly appreciated.
Probably not causing the leak, but you are end()ing the sockets after you set them to null:
clientList[i] = null;
clientList[i].end();
Shouldn't that be the other way around?
This problem can occurred because of the unbalance of speed between input stream and output stream.
Try to change the source code below.
<AS-IS>
server.on('data', function(data) {
for(var i=0;i<clientList.length;i+=1) {
try {
clientList[i].write(data);
} catch (err) {
console.log('Error writing to client "data event": ' + clientIPList[i] );
// close and null the socket on write error
try {
clientList[i] = null;
clientList[i].end();
} catch (err) {}
clientList.splice(i, 1);
clientIPList.splice(i, 1);
}
}
})
.
<TO-BE>
for(var i=0;i<clientList.length;i+=1) {
try {
server.pipe(clientList[i]);
} catch (err) {
console.log('Error writing to client "data event": ' + clientIPList[i] );
// close and null the socket on write error
try {
clientList[i] = null;
clientList[i].end();
} catch (err) {}
clientList.splice(i, 1);
clientIPList.splice(i, 1);
}
}
}
This code will adjust your memory problem.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.