From ac6375ef710d0ca9b0e4bd62e900b7d8735d7146 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 2 Apr 2026 15:03:24 -0700 Subject: [PATCH] fix: have isClosed check if sender is closed --- src/sync/mpsc.ts | 4 ++-- src/sync/oneshot.ts | 2 +- src/sync/watch.ts | 2 +- tests/sync/mpsc.test.ts | 14 ++++++++++++++ tests/sync/oneshot.test.ts | 7 +++++++ tests/sync/watch.test.ts | 7 +++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/sync/mpsc.ts b/src/sync/mpsc.ts index 11cfaf8..faad43d 100644 --- a/src/sync/mpsc.ts +++ b/src/sync/mpsc.ts @@ -185,7 +185,7 @@ export class Sender { } isClosed(): boolean { - return this.#state.closed; + return this.#dropped || this.#state.closed; } capacity(): number { @@ -373,7 +373,7 @@ export class UnboundedSender { } isClosed(): boolean { - return this.#state.closed; + return this.#dropped || this.#state.closed; } clone(): UnboundedSender { diff --git a/src/sync/oneshot.ts b/src/sync/oneshot.ts index 68b5cc7..a689722 100644 --- a/src/sync/oneshot.ts +++ b/src/sync/oneshot.ts @@ -70,7 +70,7 @@ export class OneshotSender { } isClosed(): boolean { - return this.#state.receiverClosed; + return this.#dropped || this.#state.receiverClosed; } closed(signal?: AbortSignal): Promise { diff --git a/src/sync/watch.ts b/src/sync/watch.ts index 84da0cf..e0199eb 100644 --- a/src/sync/watch.ts +++ b/src/sync/watch.ts @@ -89,7 +89,7 @@ export class WatchSender { } isClosed(): boolean { - return this.#state.receiverCount === 0; + return this.#closed || this.#state.receiverCount === 0; } close(): void { diff --git a/tests/sync/mpsc.test.ts b/tests/sync/mpsc.test.ts index a5f53c6..adf55e0 100644 --- a/tests/sync/mpsc.test.ts +++ b/tests/sync/mpsc.test.ts @@ -145,6 +145,13 @@ describe("bounded channel", () => { expect(tx.isClosed()).toBe(true); }); + it("isClosed is true after sender close", () => { + const [tx, rx] = channel(8); + tx.close(); + expect(tx.isClosed()).toBe(true); + void rx; + }); + it("closed() resolves when receiver closes", async () => { const [tx, rx] = channel(8); let resolved = false; @@ -512,6 +519,13 @@ describe("unbounded channel", () => { expect(tx.isClosed()).toBe(true); }); + it("isClosed is true after sender close", () => { + const [tx, rx] = unboundedChannel(); + tx.close(); + expect(tx.isClosed()).toBe(true); + void rx; + }); + it("closed() resolves when receiver closes", async () => { const [tx, rx] = unboundedChannel(); let resolved = false; diff --git a/tests/sync/oneshot.test.ts b/tests/sync/oneshot.test.ts index bbb667b..436967b 100644 --- a/tests/sync/oneshot.test.ts +++ b/tests/sync/oneshot.test.ts @@ -205,6 +205,13 @@ describe("oneshot", () => { expect(tx.isClosed()).toBe(true); }); + it("isClosed is true after sender close", () => { + const [tx, rx] = oneshot(); + tx[Symbol.dispose](); + expect(tx.isClosed()).toBe(true); + void rx; + }); + it("isClosed is false after send (sender closed but receiver is not)", () => { const [tx, rx] = oneshot(); tx.send(1); diff --git a/tests/sync/watch.test.ts b/tests/sync/watch.test.ts index 9dc5408..2d946be 100644 --- a/tests/sync/watch.test.ts +++ b/tests/sync/watch.test.ts @@ -105,6 +105,13 @@ describe("watch", () => { expect(tx.isClosed()).toBe(true); }); + it("isClosed is true after sender close", () => { + const [tx, rx] = watch(0); + tx.close(); + expect(tx.isClosed()).toBe(true); + void rx; + }); + it("sendIfModified returns true and wakes receivers when predicate returns true", async () => { const [tx, rx] = watch({ count: 0 }); rx.borrowAndUpdate();