diff --git a/packages/bun-usockets/src/eventing/epoll_kqueue.c b/packages/bun-usockets/src/eventing/epoll_kqueue.c index 679d8641038d..e8e36dc9d05b 100644 --- a/packages/bun-usockets/src/eventing/epoll_kqueue.c +++ b/packages/bun-usockets/src/eventing/epoll_kqueue.c @@ -30,6 +30,7 @@ void Bun__internal_dispatch_ready_poll(void* loop, void* poll); #include #include #include // memset +#include #endif void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout, uint64_t now_ns); @@ -391,6 +392,22 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout if (will_idle_inside_event_loop && loop->data.jsc_vm) Bun__JSC_onBeforeWait(loop->data.jsc_vm, now_ns); + /* The scavenger sweeps our heaps while we are in the kernel. Must come after + * Bun__JSC_onBeforeWait, which allocates: nothing may touch our heaps until the matching + * _end. mimalloc paces the sweep itself, so this costs a compare-and-swap per tick. + * With no scavenger to hand off to, fall back to sweeping inline -- but only on a tick that + * really parks, and rate-limited, because doing it between ticks is what we are avoiding. */ + const int handed_off = mi_on_thread_idle_start(); + if (!handed_off && will_idle_inside_event_loop) { + static const uint64_t idle_sweep_interval_ns = 100 * 1000000ULL; + static _Thread_local uint64_t last_idle_sweep_ns = 0; + const uint64_t sweep_now_ns = now_ns ? now_ns : us_internal_monotonic_ns(); + if (sweep_now_ns >= last_idle_sweep_ns + idle_sweep_interval_ns) { + last_idle_sweep_ns = sweep_now_ns; + mi_on_thread_idle(); + } + } + /* Fetch ready polls */ #ifdef LIBUS_USE_EPOLL /* A zero timespec already has a fast path in ep_poll (fs/eventpoll.c): @@ -411,6 +428,10 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout } while (IS_EINTR(loop->num_ready_polls)); #endif + /* Before anything can allocate again. */ + if (handed_off) + mi_on_thread_idle_end(); + us_internal_dispatch_ready_polls(loop); us_internal_drain_ready_polls(loop); us_internal_sweep_if_due(loop); diff --git a/scripts/build/deps/mimalloc.ts b/scripts/build/deps/mimalloc.ts index 6bb93084c1f7..bb507d935073 100644 --- a/scripts/build/deps/mimalloc.ts +++ b/scripts/build/deps/mimalloc.ts @@ -12,7 +12,7 @@ import type { Dependency, DirectBuild } from "../source.ts"; -const MIMALLOC_COMMIT = "13eecae8f35a73c16bdcded9291d9b56b7fc0fca"; +const MIMALLOC_COMMIT = "24211c6e7610ae7c4ec06040758ec90bd21a1c83"; export const mimalloc: Dependency = { name: "mimalloc", diff --git a/src/jsc/bindings/BunJSCEventLoop.cpp b/src/jsc/bindings/BunJSCEventLoop.cpp index 18c57e4c6610..736fdd6207fb 100644 --- a/src/jsc/bindings/BunJSCEventLoop.cpp +++ b/src/jsc/bindings/BunJSCEventLoop.cpp @@ -82,16 +82,16 @@ extern "C" void Bun__JSC_onBeforeWait(JSC::VM* _Nonnull vm, uint64_t nowNs) vm->heap.stopIfNecessary(); vm->didEnterVM = false; -#if USE(MIMALLOC) +#if USE(MIMALLOC) && OS(WINDOWS) // Collect retired pages, punch free-block holes, hand the arena purge to // the scavenger. Rate-limited; nowNs is the tick's shared reading (0 = take // one), compared by addition so an out-of-order reading cannot underflow. + // + // Windows only: everywhere else `us_loop_run_bun_tick` hands the heaps to the + // scavenger across the poll instead, so this thread never does the sweep itself. + // The libuv loop has no handoff yet, so it keeps paying for it here. static constexpr uint64_t idleSweepIntervalNs = 100 * 1000000ULL; static thread_local uint64_t lastIdleSweepNs = 0; -#if !OS(WINDOWS) - if (nowNs == 0) - nowNs = us_internal_monotonic_ns(); -#endif if (nowNs >= lastIdleSweepNs + idleSweepIntervalNs) { lastIdleSweepNs = nowNs; mi_on_thread_idle();