Context
The TypeScript runtime has no equivalent of Rust's Drop, so server-side Tx (and any other channel handle the user holds) must be closed explicitly. Forgetting to do so hangs the peer's Rx iteration waiting for end-of-stream — see 4441c73 where streaming test handlers had to add explicit output.close() calls.
Proposal
Add [Symbol.dispose]() (and [Symbol.asyncDispose]() where appropriate) to Tx and Rx so callers can opt into deterministic, scope-based cleanup with using:
async generate(count: number, output: Tx<number>): Promise<void> {
using out = output;
for (let i = 0; i < count; i++) await out.send(i);
}
Semantics:
[Symbol.dispose]() on Tx calls this.close().
[Symbol.dispose]() on Rx releases the receiver (closes the local channel, drops it from the registry).
- Channels that need to outlive the method call simply don't use
using — current explicit close() behavior is preserved.
Notes
using is lexical sugar over try/finally, not a finalizer — so leaking the handle out of scope still works the same as today (no auto-close).
Symbol.dispose is polyfilled by TS (Symbol.dispose ??= Symbol.for("Symbol.dispose")) so no runtime gating needed.
- There's a TODO comment already at
typescript/packages/vox-core/src/channeling/tx.ts:240 noting this was deferred for target-version reasons; tsgo / current targets should be fine now.
Related
- 4441c73 — workaround making handlers explicit
Context
The TypeScript runtime has no equivalent of Rust's
Drop, so server-sideTx(and any other channel handle the user holds) must be closed explicitly. Forgetting to do so hangs the peer'sRxiteration waiting for end-of-stream — see 4441c73 where streaming test handlers had to add explicitoutput.close()calls.Proposal
Add
[Symbol.dispose]()(and[Symbol.asyncDispose]()where appropriate) toTxandRxso callers can opt into deterministic, scope-based cleanup withusing:Semantics:
[Symbol.dispose]()onTxcallsthis.close().[Symbol.dispose]()onRxreleases the receiver (closes the local channel, drops it from the registry).using— current explicitclose()behavior is preserved.Notes
usingis lexical sugar overtry/finally, not a finalizer — so leaking the handle out of scope still works the same as today (no auto-close).Symbol.disposeis polyfilled by TS (Symbol.dispose ??= Symbol.for("Symbol.dispose")) so no runtime gating needed.typescript/packages/vox-core/src/channeling/tx.ts:240noting this was deferred for target-version reasons; tsgo / current targets should be fine now.Related