Skip to content

vite-plugin: server.restart() wedges permanently when an HTTP request straddles the Miniflare options swap (bundled-dev/rolldown pipeline) #15241

Description

@zhorton34

Which Cloudflare product(s) does this pertain to?

@cloudflare/vite-plugin

Versions

@cloudflare/vite-plugin 1.52.1 (latest at time of filing), wrangler 4.123.0, @cloudflare/workers-types 5.20260816.1, vite-plus 0.2.5 (Vite 8.1.5 core), node 24.x, darwin-arm64 (also reproduces on GitHub Actions macos-14)

Describe the bug

When server.restart() runs while an HTTP request is in flight to the dev server, the restart can wedge permanently: the plugin's module-scope shared context reuses the live Miniflare instance, setOptions completes ("Miniflare is ready", module runners re-initialize), but the next step — getCurrentWorkerNameToExportTypesMapfetchWorkerExportTypesminiflare.dispatchFetch(GET_EXPORT_TYPES_PATH, …) (dist/index.mjs:66627-66630, called from devPlugin.configureServer at :83080) — never resolves. configureServer never returns → _createServer never returns → server.restart() never settles. The old server's close() is never reached (verified by instrumentation), so the stale workerd lives on indefinitely.

Traffic during the restart is both necessary and sufficient (2×2 differential, 9/9 reproduction with traffic, 14/14 clean without):

  • restart with a concurrent curl poll loop → hangs 3/3 (fresh app, no D1 activity at all)
  • identical restart with no in-flight traffic → completes 3/3 (plus 11/11 across earlier trials)

OS-level signature at hang time, consistent across 7/7 hangs:

  • lsof on workerd shows exactly one entry-socket connection in CLOSE_WAIT whose peer no longer exists (the aborted straddling request) — never present in non-hang runs
  • workerd sample shows in-flight JsRpcTarget frames (a straddling request holding a multi-roundtrip module-runner RPC across the swap); workerd does not cancel a request on client disconnect
  • node side logs Internal server error: fetch failed from Miniflare.dispatchFetch via undici at restart time
  • plugin debuglog's last line ever: Fetching export types for worker "…" — the watchdog we added fires 30s later, every time

Reproduction and an honest scoping caveat

A plain-Vite worker scaffold at the same pins did NOT reproduce the permanent wedge in 8 escalating-fidelity trials (including a real options swap via config-triggered restart, a D1 binding, a 40-module chain, and tight polling — the mid-swap fetch failed abort signature reproduces, but export-types still resolves in ~3s). The permanent wedge reproduces only with Vite's bundled-dev/rolldown pipeline active (the wedged node process runs rolldown worker threads). Your 1.48.0 changelog notes recent work on exactly this codepath ("compatibility with Vite's experimental.bundledDev option, keeping Miniflare and related resources alive during mid-serve builds") — the interaction of that keep-alive behavior with an in-flight request across setOptions looks like the fault line.

Deterministic repro available: a Pioneer framework scaffold (pioneer new from github.com/find-how/pioneer.find.how, which runs the cloudflare plugin under the bundled-dev pipeline) + a curl poll loop + a config-triggered restart reproduces 9/9. Happy to provide the trial harness and captured sample/lsof evidence.

Nearest existing issues checked (not duplicates): #11171 (Bun-specific restart hang), withastro/astro#17492 (dispatchFetch POST hang).

Impact / downstream mitigation

Any framework or tooling that restarts the dev server while a client is polling (health checks, test harnesses, HMR-adjacent tooling) can permanently wedge the dev session with no error surfaced. We now mitigate downstream by gating incoming requests with a 503 during restart, which eliminates the trigger — but the underlying non-settling dispatchFetch and never-closed old server remain upstream behavior. A cancellation/timeout on the export-types fetch, or draining/aborting in-flight requests before setOptions, would fix the class.

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:vite-pluginRelating to the `@cloudflare/vite-plugin` package

    Type

    No type

    Projects

    Status
    Untriaged

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions