Skip to content

Mimalloc v3 memory usage regression #1014

@Jarred-Sumner

Description

@Jarred-Sumner

We use mimalloc in Bun and locally testing mimalloc dev3 in several of our benchmarks yielded a positive result which is exciting, but a handful of our memory leak stress tests in CI regress. I'm mostly filing this issue so you have another data point for the v3 branch - not asking you to fix a bug or anything. We will stick to v2 for now but will give v3 another go in the future.

Logs from this test:

  | 2025-02-16 02:22:19 PST | Start RSS 2704
  | 2025-02-16 02:22:19 PST | RSS Growth 227
  | 2025-02-16 02:22:19 PST | RSS Growth 198
  | 2025-02-16 02:22:19 PST | RSS Growth 203
  | 2025-02-16 02:22:19 PST | RSS Growth 275
  | 2025-02-16 02:22:19 PST | RSS Growth 91
  | 2025-02-16 02:22:19 PST | RSS Growth 150
  | 2025-02-16 02:22:19 PST | RSS Growth 283
  | 2025-02-16 02:22:19 PST | RSS Growth 151
  | 2025-02-16 02:22:19 PST | RSS Growth 246
  | 2025-02-16 02:22:19 PST | RSS Growth 66
  | 2025-02-16 02:22:19 PST | RSS Growth 262
  | 2025-02-16 02:22:19 PST |  
  | 2025-02-16 02:22:19 PST | 156 \|             lastRSS = rss;
  | 2025-02-16 02:22:19 PST | 157 \|           }
  | 2025-02-16 02:22:19 PST | 158 \|           Bun.gc(true);
  | 2025-02-16 02:22:19 PST | 159 \|
  | 2025-02-16 02:22:19 PST | 160 \|           const rss = (process.memoryUsage.rss() / 1024 / 1024) \| 0;
  | 2025-02-16 02:22:19 PST | 161 \|           expect(rss).toBeLessThan(4092);
  | 2025-02-16 02:22:19 PST | ^
  | 2025-02-16 02:22:19 PST | error: expect(received).toBeLessThan(expected)
  | 2025-02-16 02:22:19 PST |  
  | 2025-02-16 02:22:19 PST | Expected: < 4092
  | 2025-02-16 02:22:19 PST | Received: 4905
  | 2025-02-16 02:22:19 PST |  
  | 2025-02-16 02:22:19 PST | at <anonymous> (/var/lib/buildkite-agent/builds/ip-172-31-3-37/bun/bun/test/js/bun/http/bun-serve-static.test.ts:161:23)
  | 2025-02-16 02:22:19 PST |  static > /big > stress (access .body) > arrayBuffer [5291.13ms]
  | 2025-02-16 02:22:19 PST | Start RSS 7207
[OOM killer kicks in]

https://buildkite.com/bun/bun/builds/11698#01950e27-d67a-40a6-b4ec-e381cd978a17

This particular test - bun-serve-static.test.ts makes many HTTP requests that respond with a large amount of data, repeatedly appending to a buffer internally (the .body getter forces it to stream internally). HTTP requests are processed in a dedicated thread with it's own mimalloc heap. The main thread receives chunks of the HTTP response from the HTTP thread and appends to an internal buffer (which uses mi_malloc instead of the heap-specific methods).

Note that the calls to Bun.gc() internally call mi_collect(0)

 test.each(["arrayBuffer", "blob", "bytes", "text"])(
        "%s",
        async method => {
          const byteSize = static_responses[path][method]?.size;

          const bytes = method === "blob" ? static_responses[path] : await static_responses[path][method]();

          // macOS limits backlog to 128.
          // When we do the big request, reduce number of connections but increase number of iterations
          const batchSize = Math.ceil((byteSize > 1024 * 1024 ? 48 : 64) / (isWindows ? 8 : 1));
          const iterations = Math.ceil((byteSize > 1024 * 1024 ? 10 : 12) / (isWindows ? 8 : 1));

          async function iterate() {
            let array = new Array(batchSize);
            const route = `${server.url}${path.substring(1)}`;
            for (let i = 0; i < batchSize; i++) {
              array[i] = fetch(route)
                .then(res => {
                  expect(res.status).toBe(200);
                  expect(res.url).toBe(route);
                  if (label === "access .body") {
                    res.body;
                  }
                  return res[method]();
                })
                .then(output => {
                  expect(output).toStrictEqual(bytes);
                });
            }

            await Promise.all(array);

            Bun.gc();
          }

          for (let i = 0; i < iterations; i++) {
            await iterate();
          }

          Bun.gc(true);
          const baseline = (process.memoryUsage.rss() / 1024 / 1024) | 0;
          let lastRSS = baseline;
          console.log("Start RSS", baseline);
          for (let i = 0; i < iterations; i++) {
            await iterate();
            const rss = (process.memoryUsage.rss() / 1024 / 1024) | 0;
            if (lastRSS + 50 < rss) {
              console.log("RSS Growth", rss - lastRSS);
            }
            lastRSS = rss;
          }
          Bun.gc(true);

          const rss = (process.memoryUsage.rss() / 1024 / 1024) | 0;
          expect(rss).toBeLessThan(4092);
          const delta = rss - baseline;
          console.log("Final RSS", rss);
          console.log("Delta RSS", delta);
        },
        40 * 1000,
      );
    });

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions