#JSON (and maybe websocket) memory leak

1 messages · Page 1 of 1 (latest)

eager prawn
#

This comes from some weird memory behaviour in a Discord bot, so I stripped it down to just look at high throughput websockets.
This is the code for the server:

import { randomBytes } from 'crypto'

Bun.serve({
  fetch (req, server) {
    server.upgrade(req)
  },
  websocket: {
    open (ws) {
      setInterval(() => {
        // changed 200 to 10000 the second test
        const data = { d: randomBytes(200).toString('hex') }
        ws.send(JSON.stringify(data))
      }, 200)
    }
  }
})

And my client code (I just logged the rss every minute to copy into sheets):

const ws = new WebSocket(`ws://localhost:3000`)

ws.addEventListener('open', () => console.log('open'))
ws.addEventListener('close', ({ code }) => console.log('ws close, code', code))

// listener 1
ws.addEventListener('message', event => {})
// listener 2
ws.addEventListener('message', event => JSON.parse(event.data.toString()))

setInterval(() => {
  const { rss } = process.memoryUsage()
  console.log(Math.round(rss / 100_000) / 10)
}, 60_000)

I tested with neither message listener, number 1, and number 2 all simultaneously, which showed an interesting result, so I also tested with a way higher amount of data from the server. It looks like it's mainly JSON.parse causing issues, because having that listener enabled grew memory super fast, but an empty listener also seemed to cause a small problem too - so there might be multiple things going on here.

undone totem
#

Might be arrays/objects, I know in my project that just seeding the db with a ton of data requires a restart due to it using a ton of memory.

eager prawn
#

shouldn't this stuff be cleaned up though? like... it should be gc'ed right haha

#

now I'm confused

teal vigil
#

what about with bun smol?

undone totem
#

@eager prawn I think I found it, lol

eager prawn
#

oh that’s good!

eager prawn
teal vigil
#

interesting

eager prawn
#

now that I test it again smol is fine

#

lmao

pulsar sinew
#

I think bun needs a default here that's somewhere between current and --smol

#

--smol has negative runtime perf for some workloads

#

but if your machine has plenty of ram it is better

undone totem
#

I'm not sure if this counts or not, but this memory stays in use for a long time. It gets cleaned up by Bun.gc(true) though.

#

I'm not sure if it ever actually cleans the memory up. It's been at least 5 minutes now.

#
let a = [];
for (let i = 0; i < 10000000; i++) {
  a.push(async () => i);
}
a = []; 

setInterval(() => {
  console.log(process.memoryUsage().rss / 1024 / 1024);
}, 1000);

eager prawn
#

yeah same result

undone totem
#

It seems like it's based on the size?

#

Just adding a Bun.gc(true) to my code gets rid of the leak seemingly entirely.

#

@pulsar sinew is it a bug if you have to call Bun.gc(true) to make it clean up memory? if it does eventually clean it up, it didn't in the 5+ minutes I waited for it.

#

this also solves the memory being used after I seeded my db.

pulsar sinew
#

We don't ever currently schedule long gc

#

I don't think

#

because it's very very slow and its hard to find a way to pick a good time to do that

eager prawn
#

so maybe it’s better to do it manually because we can pick when to do it more precisely based on our use case?

undone totem
#

I wonder how this applies to your code @eager prawn

#

Since it's different

eager prawn
#

I'll check

undone totem
#

Is 200 bytes too big to get cleaned up 👴

#

Just call the gc true and see if it stays down

eager prawn
#

that's 200 byte packets 5 times a second

undone totem
#

Yeah with db queries and stuff it's easy to hit that

eager prawn
#

yeah we definitely hit that in prod

#

gc keeps it down yah

pulsar sinew
#

full GC would happen automatically when there's a memory pressure event

#

basically if the machine is within 20% of the ram limit

#

it will automatically run it

#

but if it isn't, it will keep stuff around for awhile if the data is potentially still visible

eager prawn
#

would be useful to have that a bit more configurable

#

although I guess it's probably alright still

#

in fairness I will probably be using smol anyway haha

#

but something in between would be more appealing

undone totem
#

I don't think smol fixes it

#

at least for my code

eager prawn
#

hm yeah neither, it did earlier... seems unpredictable

#

actually no it has worked just now

#

not sure

undone totem
#
function getMemoryUsage() {
  return process.memoryUsage().rss / 1024 / 1024;
}

const start = getMemoryUsage();

setInterval(() => {
  Bun.gc(true);

  let diff = getMemoryUsage() - start;
  if (diff > 10) {
    console.error(`Memory Diff: ${diff} MB`);
  } else if (isDevelopment()) {
    console.log(`Memory Diff: ${diff} MB`);
  }
}, 1000);
eager prawn
#

haha

undone totem
#

I fixed the code, it's kinda nice actually