microsoft / microsoft/vscode

WriteableStream.destroy leaves producers awaiting backpressure writes hung forever

Open
#332,730 0 comments 0 reactions 1 assignee Claimed by @deepak1556 View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

### Bug description

`WriteableStream.destroy()` (`src/vs/base/common/stream.ts`) clears the pending write queue without settling it:

```ts
this.pendingWritePromises.length = 0;
```

`write()` documents returning a promise the producer must await before writing more (backpressure); those promises are the queued resolvers. After a destroy, every producer parked inside `await stream.write()` stays parked forever, and nothing rejects.

The codebase treats this class of hang as a bug elsewhere: `ResourceQueue.dispose` explicitly releases its drainers "to prevent hanging promises".

Reachable through the file service copy pipeline: `src/vs/platform/files/common/io.ts` awaits `target.write(...)` between chunks with consumer streams created with a `highWaterMark`; if the target stream is destroyed mid-transfer (error or cancellation path), the read loop hangs permanently and the `finally { provider.close(handle) }` never runs, so the source file handle leaks. The token is only checked between iterations, so cancellation cannot unblock the parked write either.

Minimal repro:

1. `newWriteableStream(string, { highWaterMark: 1 })`
2. `stream.write('a')` (accepted), then `const p = stream.write('b')` (returns pending backpressure promise)
3. `stream.destroy()`
4. Observe `p` never settles.

### Expected behavior

Destroying a stream settles pending writes - by error-rejecting them (`onError`) so producers unwind and their cleanup paths run.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.