Skip to content

Commit 864f4a5

Browse files
committed
Fix Miniflare#dispose() immediately after new Miniflare() (#705)
We previously waited for Miniflare to be ready before `dispose()`ing. Unfortunately, we weren't waiting for the `workerd` config to finish being written to stdin. Calling `dispose()` immediately after `new Miniflare()` would stop waiting for socket ports to be reported, and kill the `workerd` process while data was still being written. This threw an unhandled `EPIPE` error. This changes makes sure we don't report that Miniflare is ready until after the config is fully-written. Closes #680
1 parent 139f0e5 commit 864f4a5

File tree

3 files changed

+15
-4
lines changed

3 files changed

+15
-4
lines changed

packages/miniflare/src/index.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,7 @@ export class Miniflare {
11801180
}
11811181
}
11821182

1183-
async #waitForReady() {
1183+
async #waitForReady(disposing = false) {
11841184
// If `#init()` threw, we'd like to propagate the error here, so `await` it.
11851185
// Note we can't use `async`/`await` with getters. We'd also like to wait
11861186
// for `setOptions` calls to complete before resolving.
@@ -1191,6 +1191,10 @@ export class Miniflare {
11911191
// waiters on the mutex to avoid logging ready/updated messages to the
11921192
// console if there are future updates)
11931193
await this.#runtimeMutex.drained();
1194+
// If we called `dispose()`, we may not have a `#runtimeEntryURL` if we
1195+
// `dispose()`d synchronously, immediately after constructing a `Miniflare`
1196+
// instance. In this case, return a discard URL which we'll ignore.
1197+
if (disposing) return new URL("http://[100::]/");
11941198
// `#runtimeEntryURL` is assigned in `#assembleAndUpdateConfig()`, which is
11951199
// called by `#init()`, and `#initPromise` doesn't resolve until `#init()`
11961200
// returns.
@@ -1465,7 +1469,7 @@ export class Miniflare {
14651469
async dispose(): Promise<void> {
14661470
this.#disposeController.abort();
14671471
try {
1468-
await this.ready;
1472+
await this.#waitForReady(/* disposing */ true);
14691473
} finally {
14701474
// Remove exit hooks, we're cleaning up what they would've cleaned up now
14711475
this.#removeTmpPathExitHook();

packages/miniflare/src/runtime/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from "assert";
22
import childProcess from "child_process";
3-
import type { Abortable } from "events";
3+
import { Abortable, once } from "events";
44
import rl from "readline";
55
import { Readable } from "stream";
66
import { red } from "kleur/colors";
@@ -146,9 +146,10 @@ export class Runtime {
146146
const controlPipe = runtimeProcess.stdio[3];
147147
assert(controlPipe instanceof Readable);
148148

149-
// 3. Write config
149+
// 3. Write config, and wait for writing to finish
150150
runtimeProcess.stdin.write(configBuffer);
151151
runtimeProcess.stdin.end();
152+
await once(runtimeProcess.stdin, "finish");
152153

153154
// 4. Wait for sockets to start listening
154155
return waitForPorts(controlPipe, options);

packages/miniflare/test/index.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,12 @@ test("Miniflare: listens on ipv6", async (t) => {
709709
t.true(response.ok);
710710
});
711711

712+
test("Miniflare: dispose() immediately after construction", async (t) => {
713+
const mf = new Miniflare({ script: "", modules: true });
714+
await mf.dispose();
715+
t.pass();
716+
});
717+
712718
test("Miniflare: getBindings() returns all bindings", async (t) => {
713719
const tmp = await useTmp(t);
714720
const blobPath = path.join(tmp, "blob.txt");

0 commit comments

Comments
 (0)