Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak? #804

Closed
ghost opened this issue Aug 5, 2016 · 21 comments
Closed

Memory leak? #804

ghost opened this issue Aug 5, 2016 · 21 comments

Comments

@ghost
Copy link

ghost commented Aug 5, 2016

my code:

var WebSocketServer = require('ws').Server
  , wss = new WebSocketServer({ port: 8081 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });

  ws.send('something');
});

When 20000 clients connected, memory usage is 2.864 GB.
Then close all clients,
After a few minute, memory usage is 2.188GB.
And, after 20000 new clients connected, memory usage is 4.137 GB.
It looks like that memory did not free.

@ghost
Copy link
Author

ghost commented Aug 8, 2016

node --expose-gc server.js
After call gc(); every 30 sec.

setInterval(function () {
  console.log("Before:",process.memoryUsage());
  gc();
  console.log(" After:",process.memoryUsage());
},30000);
Before: { rss: 43212800, heapTotal: 35234912, heapUsed: 21054984 } // rss 40MB
 After: { rss: 34332672, heapTotal: 20804448, heapUsed: 16247984 }
Before: { rss: 34361344, heapTotal: 20804448, heapUsed: 17081824 }
 After: { rss: 33681408, heapTotal: 21836384, heapUsed: 16218136 }

// connecting...

Before: { rss: 538062848, heapTotal: 131204960, heapUsed: 105930352 }
 After: { rss: 539049984, heapTotal: 132236896, heapUsed: 89826384 }
Before: { rss: 849797120, heapTotal: 185897568, heapUsed: 153704744 }
 After: { rss: 818884608, heapTotal: 154956128, heapUsed: 139841496 }

// close all connections (5000 clients)

Before: { rss: 843161600, heapTotal: 197248864, heapUsed: 142158408 } // rss 800mb
 After: { rss: 840036352, heapTotal: 194153056, heapUsed: 84102104 }
Before: { rss: 840249344, heapTotal: 195172960, heapUsed: 93208432 }
 After: { rss: 767713280, heapTotal: 121946208, heapUsed: 66619920 }
Before: { rss: 767762432, heapTotal: 121946208, heapUsed: 70858376 }
 After: { rss: 714121216, heapTotal: 86860384, heapUsed: 66315040 }
Before: { rss: 714420224, heapTotal: 87892320, heapUsed: 66653904 }
 After: { rss: 705867776, heapTotal: 79636832, heapUsed: 66268472 }
Before: { rss: 706244608, heapTotal: 79636832, heapUsed: 69060584 }
 After: { rss: 703946752, heapTotal: 77572960, heapUsed: 66125224 }
Before: { rss: 704274432, heapTotal: 78604896, heapUsed: 68620712 }
 After: { rss: 703889408, heapTotal: 78604896, heapUsed: 66144408 }
Before: { rss: 704372736, heapTotal: 78604896, heapUsed: 66524424 }
 After: { rss: 704102400, heapTotal: 78604896, heapUsed: 66164032 }
Before: { rss: 704462848, heapTotal: 80656736, heapUsed: 68852232 }
 After: { rss: 704147456, heapTotal: 79636832, heapUsed: 66150056 } // rss 700mb
Before: { rss: 704675840, heapTotal: 79636832, heapUsed: 68907184 }

Tested in node 4.4.7 and node 6.3.1, the result is similar.

@rainder
Copy link

rainder commented Aug 22, 2016

Facing the same issue. any help?

@ericmdantas
Copy link

Looks like the clients array is still full, could you guys log ws.clients.length and share what's shown?

@rainder
Copy link

rainder commented Aug 24, 2016

ws.clients.length returns 0

@rainder
Copy link

rainder commented Aug 24, 2016

i've done some tests overnight.

client.js

'use strict';

const WS = require('ws');

let counter = 0;

(function create() {
  const ws = new WS('ws://10.11.241.244:8081');
  const data = 'somedata';

  ws.on('error', err => console.error(err));
  ws.on('open', open);

  function open() {
    ws.send(data);
    process.stdout.write('.');

    if (++counter < 5000) {
      create();
    }
  }
})();

server.js

'use strict';

const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({ port: 8081 });

let connections = 0;
let messages = 0;

wss.on('connection', function connection(ws) {
  connections++;

  ws.on('message', function incoming(message) {
    messages++;
  });

  ws.on('close', function () {
    connections--;
  })

  ws.send('something');
});

(function report() {
  const usage = process.memoryUsage();
  for (let key of Object.keys(usage)) {
    usage[key] = Math.round(usage[key] / 1024 / 1024) + 'MB';
  }

  console.log(new Date());
  console.log('  MEM:', usage);
  console.log('  STATS:', { connections, messages, clients: wss.clients.length });
  console.log('');

  setTimeout(report, 5000);
})();

process

  • launched server on gcc (node:6.2.1)
  • launched client and scaled up to 4 replicas
  • waited until 20k connections were established
  • killed client docker containers
  • waited at least 1minute for GC to kick in couple of times

before each test server was restarted and code was modified (only ws.send commented or uncommented on client or/and server side)

results

20k connections; no messages (ws.send commented on both client and server)
rss: 374MB dropped to 114MB (after all connections were closed and gc)

20k connections; 20k incoming messages (client -> server); 1 message per connection
rss: 740MB => 364MB

20k connections; 20k outgoing messages (server -> client); 1 message per connection
rss: 2306MB => 2022MB

20k connections; 20k incoming and 20k outgoing messages; 1in/1out message per connection
rss: 2724MB => 2321MB

seems like something doggy is going on in ws.send() method on the server side.

@rainder
Copy link

rainder commented Aug 24, 2016

also, if I run a code which:

  • creates a new connection right after previous was created
  • closes a connection after 30s
  • sends 1 message and receives 1 message from the server per connection
'use strict';

const WS = require('ws');

(function create() {
  const ws = new WS('ws://10.11.241.244:8081');
  const data = 'somedata';

  ws.on('error', err => console.error(err));
  ws.on('open', open);

  function open() {
    ws.send(data);
    process.stdout.write('.');
    create();
  }

  setTimeout(function () {
    ws.close();
  }, 30000);
})();

the memory (on both sides!) grows indefinitely until process is killed by os https://jpst.it/MzKj

@yuyi
Copy link

yuyi commented Sep 3, 2016

I faced same issue here.
It is an very important issue, because it can be used to attack ws server.

This code has issue:

'use strict';

const WS = require('ws');

let counter = 0;

(function create() {
  const ws = new WS('ws://127.0.0.1:8081');
  const data = 'somedata';

  ws.on('error', err => console.error(err));
  ws.on('open', open);

  function open() {
    ws.send(data);
    process.stdout.write('.');
      setTimeout(
        () => {ws.close();},
        10000 //DIFFERENT HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      );
    if (++counter < 10000) {

      create();
      //setTimeout(
      //create, 10);
    }
  }
})();

and This code has NO issue:

'use strict';

const WS = require('ws');

let counter = 0;

(function create() {
  const ws = new WS('ws://127.0.0.1:8081');
  const data = 'somedata';

  ws.on('error', err => console.error(err));
  ws.on('open', open);

  function open() {
    ws.send(data);
    process.stdout.write('.');
      setTimeout(
        () => {ws.close();},
        0  //DIFFERENT HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      );
    if (++counter < 10000) {

      create();
      //setTimeout(
      //create, 10);
    }
  }
})();
[8119:0x1b890b0]   217563 ms: Mark-sweep 81.9 (178.1) -> 33.5 (147.1) MB, 143.0 / 4 ms [Isolate::RequestGarbageCollection] [GC in old space requested].
[8119:0x1b890b0] Memory allocator,   used: 150608 KB, available: 1348528 KB
[8119:0x1b890b0] New space,          used:      3 KB, available:  16120 KB, committed:  32248 KB
[8119:0x1b890b0] Old space,          used:  31987 KB, available:  19498 KB, committed: 107829 KB
[8119:0x1b890b0] Code space,         used:   2017 KB, available:      0 KB, committed:   3229 KB
[8119:0x1b890b0] Map space,          used:    300 KB, available:      3 KB, committed:   1070 KB
[8119:0x1b890b0] Large object space, used:      0 KB, available: 1347487 KB, committed:      0 KB
[8119:0x1b890b0] All spaces,         used:  34309 KB, available: 1383110 KB, committed: 144377 KB
[8119:0x1b890b0] External memory reported: 104439 KB
[8119:0x1b890b0] Total time spent in GC  : 3456.1 ms
Sun Sep 04 2016 00:42:35 GMT+0800 (CST)
  MEM: { rss: '1007MB', heapTotal: '141MB', heapUsed: '34MB' }
  STATS: { connections: 622, messages: 30000, clients: 622 }

The leaked memory is not in GC Collection... really weird...

@rainder
Copy link

rainder commented Sep 4, 2016

I have managed to reduce leakage by setting 'perMessageDeflate: false'. I've dumped rss mem and found a weird endless recursive call in perMessageDeflate->zlib->_onTimeout->_onTimeout->... method (I don't remember now if these are exact method names, but at least you have a guidance). Didn't had time to dig deeper into zlib implementation.

@lpinca
Copy link
Member

lpinca commented Oct 24, 2016

I can confirm and reproduce the issue as described by @3490.
It happens only when permessage-deflate is enabled and I think the cause is explained here nodejs/node#8871.

@leebenson
Copy link

leebenson commented Dec 3, 2016

this seems like a pretty major issue... is there an ETA for a fix? or is this is an issue in node proper?

@lpinca
Copy link
Member

lpinca commented Dec 4, 2016

@leebenson I agree but I think there is not much we can do about it as this is an external issue.

@lpinca
Copy link
Member

lpinca commented Dec 4, 2016

Maybe we should just disable permessage-deflate by default.

@ghost
Copy link

ghost commented May 17, 2017

Even if disable permessage-deflate ,there still Memory leak, just less.

for example
var WebSocket = require('ws');
var ws = new WebSocket('ws://www.host.com/path', {
perMessageDeflate: false
});

var WebSocket = require('ws');
var wss = new WebSocket.Server({
perMessageDeflate: false,
port: 8080
});

open the server ,it cost memory usage is 20 MB
Then 20Kclients connected, memory usage is 400MB
Then close all clients,
After a few minute,memory usage is 80MB and it dont decrease any more
It means there are 60M memory are oppuied and will not be clean.
@lpinca

@lpinca
Copy link
Member

lpinca commented May 17, 2017

@binginto on the first run this is normal, you have to wait for it "stabilize". Run it multiple times, if memory does not grow every time, then it works as expected.

@ghost
Copy link

ghost commented May 17, 2017

I dont know what means 'stabilize',is the 60MB memory will be clean by GC after a period of time?

Now, i found the Situation is if it "stabilize",if connnet and close severtime. the memory does not grow every time.just oppuied 60MB at first time and it not decrease in 10 min.

And it grows with the number of the clients. for example,it 20K clients connected and closed. it oppuied 60MB and will not clean by gc. if 5k clients, it just it oppuied 20MB and will not clean by gc. and if 50K clients ,it oppuied 140MB and not decrease in 10 min.

And I dont know Is this Situation works as expected and it isnt Memory leak?

@lpinca
Copy link
Member

lpinca commented May 17, 2017

@binginto here is an example:

server.js
'use strict';

const WebSocket = require('ws');

const wss = new WebSocket.Server({
  perMessageDeflate: false,
  port: 3000
});

wss.on('connection', (ws) => ws.send('something'));

const report = () => {
  gc();
  const rss = process.memoryUsage().rss / 1024 / 1024;
  console.log('clients: %d, rss: %d', wss.clients.size, rss);
};

setInterval(report, 30000);
report();
client.js
'use strict';

const WebSocket = require('ws');

let cnt = 0;

(function create() {
  const ws = new WebSocket('ws://localhost:3000');

  ws.on('error', err => console.error(err));
  ws.on('open', () => {
    ws.send('something');
    if (++cnt !== 20000) create();
  });
})();

This is what I get by starting and killing the process that runs the client multiple times:

$ node --expose-gc server.js
clients: 0, rss: 25.859375
clients: 20000, rss: 202.23828125
clients: 20000, rss: 171.6953125
clients: 0, rss: 72.49609375
clients: 0, rss: 60.27734375
clients: 18728, rss: 196.94921875
clients: 20000, rss: 178.66015625
clients: 20000, rss: 176.77734375
clients: 0, rss: 73.7890625
clients: 0, rss: 68.15625
clients: 19205, rss: 205.9140625
clients: 20000, rss: 180.65625
clients: 20000, rss: 175.9375
clients: 0, rss: 103.140625
clients: 0, rss: 68.33203125
clients: 19468, rss: 201.62109375
clients: 20000, rss: 180.75
clients: 20000, rss: 177.63671875
clients: 0, rss: 90.57421875
clients: 0, rss: 67.6484375
clients: 19242, rss: 199.44140625
clients: 20000, rss: 182
clients: 20000, rss: 177.36328125
clients: 0, rss: 112.93359375
clients: 0, rss: 48.7734375
clients: 0, rss: 48.7734375

As you can see memory usage does not grow every time and is stable at ~180 max / ~60 min MiB.

@ghost
Copy link

ghost commented May 18, 2017

@lpinca why if a new client connect to server and then closed it .it still oppuied some space ? If the server cant clean it any more . Now ,it cause a problem , there are many new client connnect to the server and then close. This action cost some memory, as time when by , the server will lost all memory and have to restart . Is it true ? if it is true ,the memory leak still exist , is it right?

@lpinca
Copy link
Member

lpinca commented May 18, 2017

If a new client connect to server and then closed it .it still oppuied some space ?

No, once it is GC'ed the retained memory is freed. You can also verify this by taking heap snapshots.

This action cost some memory, as time when by , the server will lost all memory and have to restart . Is it true ? if it is true ,the memory leak still exist , is it right?

No, unless proven otherwise, there is no leak.

P.S. if you run the above example on Linux with permessage-deflate enabled (perMessageDeflate: { threshold: 0 }), you'll see that there is a leak.

@binginto
Copy link

binginto commented May 19, 2017

@lpinca
Like this

server

var WebSocket = require('ws');

var wss = new WebSocket.Server({
  perMessageDeflate: false,
  port:8001
});

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });
  ws.send('something');
});

client

var WebSocket = require('ws');

function cerateclient(){
  var ws = new WebSocket("ws://localhost:8001/");
  ws.onopen = function() {
   ws.send('connnect');
  };

  ws.onmessage = function(evt) {
   console.log(evt.data);
  };
}

 for(var i =0 ; i  < 200000 || i == 20000; i++){
      cerateclient();
 }

why after 20000 times connect and close ? Then wait serval hours ,many times of gc cleaned no used memory. The rss still will not back to the original level, and it still larger than the original level .Also, the
more clients connect and close .The rss is larger. I cant understand this point ,which oppuied the memory ?

@lpinca
Copy link
Member

lpinca commented May 19, 2017

It won't go back to the startup level (~20 MiB), this is how it works.
The same happens with a plain HTTP server. When you start the process it uses ~20 MiB. After you use it for a while it stabilizes at ~60 MiB when in idle.

@lpinca
Copy link
Member

lpinca commented Sep 15, 2017

Partially fixed by #1204, closing.

@lpinca lpinca closed this as completed Sep 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants