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

Bun leaks memory in Workers #5709

Open
jack-weilage opened this issue Sep 19, 2023 · 12 comments
Open

Bun leaks memory in Workers #5709

jack-weilage opened this issue Sep 19, 2023 · 12 comments
Labels
atw bug Something isn't working node.js Compatibility with Node.js APIs

Comments

@jack-weilage
Copy link

What version of Bun is running?

1.0.3+f77df12894e7952ea58605432bf9f6d252fff273

What platform is your computer?

Linux 5.15.90.1-microsoft-standard-WSL2 x86_64 x86_64

What steps can reproduce the bug?

Create a main file which starts up a worker, waits until it completes its work, then logs memory usage.

async function run() {
  for (let i = 0; i < 100; i++) {
    await new Promise((resolve) => {
      const worker = new Worker('./worker.ts')

      const on_message = () => {
        worker.removeEventListener('message', on_message)
        resolve(1)
      }

      worker.addEventListener('message', on_message)
      worker.postMessage('start')
    })

    console.log(process.memoryUsage().rss)
  }
}

run()

And a worker which allocates a large amount of memory

function on_message() {
  const arrays = []
  for (let i = 0; i < 1_000_000; i++) {
    arrays.push(new Array(100).fill(i))
  }

  self.postMessage(arrays.length)
  self.removeEventListener('message', on_message)
}

self.addEventListener('message', on_message)

Then run main with bun run main.ts

What is the expected behavior?

Memory usage should spike as the worker begins, then drop sometime afterwards as garbage collection is run. The script should continue running until the loop completes.

When a (functionally) identical script and worker (code below) is run via Node, memory usage stays around the same every call.

import { Worker } from 'worker_threads'

async function run() {
  for (let i = 0; i < 100; i++) {
    await new Promise((resolve) => {
      const worker = new Worker('./node/worker.js')

      const on_message = () => {
        worker.removeListener('message', on_message)
        resolve(1)
      }

      worker.addListener('message', on_message)
      worker.postMessage('start')
    })
    console.log(i)
  }
}

run()
import { parentPort } from 'worker_threads'

function on_message() {
  const arrays = []
  for (let i = 0; i < 1_000_000; i++) {
    arrays.push(new Array(100).fill(i))
  }

  parentPort.postMessage(arrays.length)
  parentPort.removeListener('message', on_message)
}

parentPort.addListener('message', on_message)

Results in node:

> node node/main.js
971173888
983752704
979537920
988639232
986144768
979836928
984797184
986599424
985792512
^C

What do you see instead?

Memory usage continues to rise, eventually crashing (saturates 8gb of RAM and 2gb of swap) at around the 8th iteration.

Results in Bun:

> bun bun/main.ts
1050456064
2005811200
2962059264
3915100160
4867907584
5819809792
6765035520
^C

Additional information

  • Adding smol to either bun run or new Worker() has no effect.
  • I fixed this by calling Bun.gc(true) immediately after self.postMessage() in a personal project, but couldn't reproduce the results here.
  • I don't have enough knowledge of swap memory to know if this is unexpected, but after crashing, the swap usage often grows.
@jack-weilage jack-weilage added the bug Something isn't working label Sep 19, 2023
@jack-weilage jack-weilage changed the title Bun runs out of memory when using Workers Bun doesn't clean up memory used by Workers Sep 19, 2023
@jack-weilage jack-weilage changed the title Bun doesn't clean up memory used by Workers Bun leaks memory in Workers Sep 19, 2023
@AriPerkkio
Copy link

I think I've run into similar issue when using Bun's Worker's. This error message is being thrown and Bun crashes when memory leaks enough:

FATAL ERROR: JavaScript garbage collection failed because thread_get_state returned an error (268435459). This is probably the result of running inside Rosetta, which is not supported.
/Users/jarred/actions-runner/_work/WebKit/WebKit/Source/WTF/wtf/posix/ThreadingPOSIX.cpp(497) : size_t WTF::Thread::getRegisters(const WTF::ThreadSuspendLocker &, WTF::PlatformRegisters &)

@Electroid Electroid added the node.js Compatibility with Node.js APIs label Sep 19, 2023
@Electroid
Copy link
Contributor

Thanks for reporting, this is a known issue that we will be fixing.

@jack-weilage
Copy link
Author

FATAL ERROR: JavaScript garbage collection failed because thread_get_state returned an error (268435459). This is probably the result of running inside Rosetta, which is not supported.
/Users/jarred/actions-runner/_work/WebKit/WebKit/Source/WTF/wtf/posix/ThreadingPOSIX.cpp(497) : size_t WTF::Thread::getRegisters(const WTF::ThreadSuspendLocker &, WTF::PlatformRegisters &)

Interesting! In my case, Bun never returned an error before crashing.

@jack-weilage
Copy link
Author

Any follow-up on when this might be fixed? This is preventing me from using Workers in bun.

@jack-weilage
Copy link
Author

I just re-ran the reproduction code and the memory leak seems to have been mostly fixed somewhere between v1.0.11 and v1.0.12. Memory usage used to double every iteration, but now (in my testing) memory usage went from 1013653504 to 1034555392 after 1000 iterations. That averages out to ~2000 per iteration, as compared to ~1000000000 per iteration when I originally reported.

@jack-weilage
Copy link
Author

Should this issue be closed (the massive memory leak is fixed), or kept open (there still seems to be a tiny leak left over)?

@chytreg
Copy link

chytreg commented Mar 18, 2024

It's still valid for me for the most recent released version (1.0.33) even for canary :(
The memory is growing on each worker passage, it's not free at all even if the worker has been terminated.

@MeguminSama
Copy link

I was running into issues possibly caused by #5659 so I tried to use workers as a workaround, hoping that when the worker gets terminated, the memory might be freed. Unfortunately, this doesn't seem to be the case.

@kingkong404
Copy link

kingkong404 commented May 8, 2024

Currently a major issue impacting us using Bun in production.

@Electroid Electroid added the atw label May 8, 2024
@rafrafek
Copy link

Collab with Deno maybe? denoland/deno#18414

@kingkong404
Copy link

Please fix this! The memory just keeps increasing until server runs out of memory crashes.

@reddec
Copy link

reddec commented Nov 21, 2024

Made investigation

  • Bun: 1.1.36
  • OS: Linux arch 6.11.8-arch1-2 #1 SMP PREEMPT_DYNAMIC Fri, 15 Nov 2024 15:35:07 +0000 x86_64 GNU/Linux

Sample code (index.ts)

import { heapStats } from "bun:jsc";

function spawnWorker() {
  return new Promise((res, rej) => {
    const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
      type: "module",
      ref: false,
      smol: true,
    });
    worker.onerror = (e) => rej(e);
    worker.addEventListener('close', () => {
      res(worker)
    })
    worker.onmessage = (msg) => {
      if (msg.data === "ready") {
        worker.terminate();
      }
    }
  })
}

Bun.gc(true);
const start = heapStats().objectTypeCounts

for (let i = 0; i < 1000; i++) {
  await spawnWorker()
  Bun.gc(true);
  const stats = heapStats();
  await Bun.write(`data/${i}.json`, JSON.stringify(stats))
}

Worker code (worker.ts)

self.postMessage("ready")

Made 1000 iterations:

Heap/Objects

Image

Per objects

Image

FunctionCodeBlock

Image

Reproduce:

  • Unpack and run make. Requires gnuplot to plot charts

bun-5709.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
atw bug Something isn't working node.js Compatibility with Node.js APIs
Projects
None yet
Development

No branches or pull requests

8 participants