cloudflare / cloudflare/workers-sdk

Remote D1 sequential queries leak established workerd sockets in local Vite development

Open
#15,462 1 comment 0 reactions 0 assignees View on GitHub
feature:remote-bindings package:vite-plugin product:d1
Dominant language
TypeScript
Stars
4.5k
Forks
1.5k
Avg merge
3d 8h
Merged PRs (30d)
186

Description

### Which Cloudflare product(s) does this pertain to?

Wrangler, Miniflare, the Cloudflare Vite plugin, workerd, and remote D1 bindings.

### Existing issue search

I searched this tracker for remote D1 sequential-query socket growth, remote D1 port exhaustion, and D1 WebSocket leaks. I did not find an issue covering successful sequential remote D1 queries accumulating established sockets. Adjacent port-exhaustion and remote-binding lifecycle reports do not contain this D1-specific reproduction.

### What version(s) are you using?

- `@cloudflare/vite-plugin@1.54.3`
- `wrangler@4.128.0`
- `miniflare@5.20260831.0-alpha`
- `workerd@1.20260831.1`
- macOS

The same behavior reproduced with Wrangler `4.127.1` and an older application dependency snapshot.

### What is the problem?

A local Worker using a Wrangler D1 binding configured with `remote: true` accumulates established TCP connections after each successful sequential D1 query. Connections do not return near the idle baseline after settling. Continued local development eventually exhausts ephemeral ports and breaks unrelated local network clients.

No browser is required. Local observability was disabled. Idle runtime and non-D1 requests remained stable in the same bounded test. Separate bounded R2-read and Vectorize-read checks also remained stable; this report claims only that D1 is sufficient to reproduce the leak.

### Minimal reproduction

1. Configure the Cloudflare Vite plugin and a real remote D1 binding:

```jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "example",
"database_id": "",
"remote": true
}
]
}
```

2. Add one unauthenticated local-only route that performs exactly one read and returns its result:

```ts
const row = await env.DB
.prepare("SELECT COUNT(*) AS count FROM an_existing_remote_table")
.first<{ count: number }>();
return Response.json(row);
```

3. Start Vite with no browser open and local observability disabled.
4. Identify the two local `workerd` processes and record their established TCP socket counts.
5. Send 20 sequential HTTP requests to the route, waiting for each successful response before sending the next.
6. Record socket counts after requests 1, 10, and 20, then again after ten idle seconds.

Every request in the valid run returned HTTP 200 and the same real remote count (`2561`). The two temporary `workerd` processes showed:

| Measurement | Process A | Process B |
| --- | ---: | ---: |
| Idle | 0 | 2 |
| After request 1 | 4 | 5 |
| After request 10 | 22 | 14 |
| After request 20 | 42 | 24 |
| Ten seconds after request 20 | 40 | 22 |

The test process was then terminated and the disposable test copy removed.

### Expected behavior

Successful sequential remote D1 queries should reuse or close their remote-binding transport connections. Established socket counts should remain bounded and return near the idle baseline after requests settle.

### Actual behavior

Each successful sequential D1 query adds established connections across the local `workerd` processes. Almost all remain established after the test settles, producing approximately linear connection growth.

### Additional context

Wrangler `4.128.0` still creates a fresh Cap'n Web WebSocket RPC session inside `makeRemoteProxyStub` in `packages/miniflare/src/workers/shared/remote-bindings-utils.ts`. The reproduction above does not prove that implementation detail is the root cause, but it is consistent with the per-query connection growth and may be a useful place to begin investigation.

Contributor guide

Open the contributing guide

Research direction

Start in packages/miniflare/src/workers/shared/remote-bindings-utils.ts, especially makeRemoteProxyStub, and trace how remote D1 requests create and settle transport connections. Reproduce the issue with the Cloudflare Vite plugin, a remote D1 binding, and 20 sequential requests while recording the two workerd processes' established socket counts. Done means successful sequential queries leave socket counts bounded and near the idle baseline after ten seconds.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
databases, networking, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.