From 34827b3347ae2a001ac1fb7d3934d222f64b9f99 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 7 Jan 2024 05:57:56 +0900 Subject: [PATCH 1/6] Upgrade devtools --- .github/workflows/test.yml | 4 +-- .github/workflows/udd.yml | 50 -------------------------------------- deno.jsonc | 13 +++++++--- deno.lock | 20 --------------- 4 files changed, 12 insertions(+), 75 deletions(-) delete mode 100644 .github/workflows/udd.yml delete mode 100644 deno.lock diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 60717c1..25652c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} @@ -30,7 +30,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} diff --git a/.github/workflows/udd.yml b/.github/workflows/udd.yml deleted file mode 100644 index 149b884..0000000 --- a/.github/workflows/udd.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Update - -on: - schedule: - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - udd: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: denoland/setup-deno@v1 - with: - deno-version: "1.x" - - name: Update dependencies - run: | - deno task upgrade > ../output.txt - env: - NO_COLOR: 1 - - name: Read ../output.txt - id: log - uses: juliangruber/read-file-action@v1 - with: - path: ../output.txt - - name: Commit changes - run: | - git config user.name '${{ github.actor }}' - git config user.email '${{ github.actor }}@users.noreply.github.com' - git commit -a -F- < Date: Sun, 7 Jan 2024 06:01:56 +0900 Subject: [PATCH 2/6] Use `$MODULE_VERSION` to refer to the current version of the module --- README.md | 28 ++++++++++++++-------------- barrier.ts | 2 +- lock.ts | 4 ++-- mutex.ts | 4 ++-- notify.ts | 4 ++-- queue.ts | 2 +- rw_lock.ts | 4 ++-- semaphore.ts | 2 +- stack.ts | 2 +- testutil.ts | 2 +- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d20f10d..2d16e34 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Asynchronous primitive modules for [Deno][deno]. until all of them have reached a certain point of execution before continuing. ```ts -import { Barrier } from "./barrier.ts"; +import { Barrier } from "https://deno.land/x/async@$MODULE_VERSION/barrier.ts"; const barrier = new Barrier(3); @@ -38,8 +38,8 @@ worker(3); shared value. ```ts -import { AsyncValue } from "./testutil.ts"; -import { Lock } from "./lock.ts"; +import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; +import { Lock } from "https://deno.land/x/async@$MODULE_VERSION/lock.ts"; // Critical section const count = new Lock(new AsyncValue(0)); @@ -55,8 +55,8 @@ as long as there are no writers holding the lock. Writers block all other readers and writers until the write operation completes. ```ts -import { AsyncValue } from "./testutil.ts"; -import { RwLock } from "./rw_lock.ts"; +import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; +import { RwLock } from "https://deno.land/x/async@$MODULE_VERSION/rw_lock.ts"; const count = new RwLock(new AsyncValue(0)); @@ -86,8 +86,8 @@ This is a low-level primitive. Use `Lock` instead of `Mutex` if you need to access a shared value concurrently. ```ts -import { AsyncValue } from "./testutil.ts"; -import { Mutex } from "./mutex.ts"; +import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; +import { Mutex } from "https://deno.land/x/async@$MODULE_VERSION/mutex.ts"; const count = new AsyncValue(0); @@ -113,8 +113,8 @@ notification. ```ts import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { promiseState } from "./state.ts"; -import { Notify } from "./notify.ts"; +import { promiseState } from "https://deno.land/x/async@$MODULE_VERSION/state.ts"; +import { Notify } from "https://deno.land/x/async@$MODULE_VERSION/notify.ts"; const notify = new Notify(); const waiter1 = notify.notified(); @@ -134,7 +134,7 @@ with optional waiting when popping elements from an empty queue. ```ts import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { Queue } from "./queue.ts"; +import { Queue } from "https://deno.land/x/async@$MODULE_VERSION/queue.ts"; const queue = new Queue(); queue.push(1); @@ -150,7 +150,7 @@ with optional waiting when popping elements from an empty stack. ```ts import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { Stack } from "./stack.ts"; +import { Stack } from "https://deno.land/x/async@$MODULE_VERSION/stack.ts"; const stack = new Stack(); stack.push(1); @@ -167,7 +167,7 @@ A semaphore that allows a limited number of concurrent executions of an operation. ```ts -import { Semaphore } from "./semaphore.ts"; +import { Semaphore } from "https://deno.land/x/async@$MODULE_VERSION/semaphore.ts"; const sem = new Semaphore(5); const worker = () => { @@ -184,7 +184,7 @@ await Promise.all([...Array(10)].map(() => worker())); purpose. ```typescript -import { promiseState } from "https://deno.land/x/async/mod.ts"; +import { promiseState } from "https://deno.land/x/async@$MODULE_VERSION/mod.ts"; const p1 = Promise.resolve("Resolved promise"); console.log(await promiseState(p1)); // fulfilled @@ -203,7 +203,7 @@ asynchronously. ```ts import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { AsyncValue } from "./testutil.ts"; +import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; const v = new AsyncValue(0); assertEquals(await v.get(), 0); diff --git a/barrier.ts b/barrier.ts index d5848e3..b7d35be 100644 --- a/barrier.ts +++ b/barrier.ts @@ -10,7 +10,7 @@ import { Notify } from "./notify.ts"; * unblock and continue executing. * * ```ts - * import { Barrier } from "./barrier.ts"; + * import { Barrier } from "https://deno.land/x/async@$MODULE_VERSION/barrier.ts"; * * const barrier = new Barrier(3); * diff --git a/lock.ts b/lock.ts index 454ee27..2984cb5 100644 --- a/lock.ts +++ b/lock.ts @@ -4,8 +4,8 @@ import { Mutex } from "./mutex.ts"; * A mutual exclusion lock that provides safe concurrent access to a shared value. * * ```ts - * import { AsyncValue } from "./testutil.ts"; - * import { Lock } from "./lock.ts"; + * import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; + * import { Lock } from "https://deno.land/x/async@$MODULE_VERSION/lock.ts"; * * // Critical section * const count = new Lock(new AsyncValue(0)); diff --git a/mutex.ts b/mutex.ts index 2db634d..e78d774 100644 --- a/mutex.ts +++ b/mutex.ts @@ -11,8 +11,8 @@ import { * concurrently. * * ```ts - * import { AsyncValue } from "./testutil.ts"; - * import { Mutex } from "./mutex.ts"; + * import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; + * import { Mutex } from "https://deno.land/x/async@$MODULE_VERSION/mutex.ts"; * * const count = new AsyncValue(0); * diff --git a/notify.ts b/notify.ts index 4ee71c2..c6f6f66 100644 --- a/notify.ts +++ b/notify.ts @@ -12,8 +12,8 @@ export type WaitOptions = { * * ```ts * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; - * import { promiseState } from "./state.ts"; - * import { Notify } from "./notify.ts"; + * import { promiseState } from "https://deno.land/x/async@$MODULE_VERSION/state.ts"; + * import { Notify } from "https://deno.land/x/async@$MODULE_VERSION/notify.ts"; * * const notify = new Notify(); * const waiter1 = notify.notified(); diff --git a/queue.ts b/queue.ts index c701e5c..36c10f8 100644 --- a/queue.ts +++ b/queue.ts @@ -6,7 +6,7 @@ import { Notify, WaitOptions } from "./notify.ts"; * * ```ts * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; - * import { Queue } from "./queue.ts"; + * import { Queue } from "https://deno.land/x/async@$MODULE_VERSION/queue.ts"; * * const queue = new Queue(); * queue.push(1); diff --git a/rw_lock.ts b/rw_lock.ts index 9eb3337..5a62616 100644 --- a/rw_lock.ts +++ b/rw_lock.ts @@ -6,8 +6,8 @@ import { Mutex } from "./mutex.ts"; * Writers block all other readers and writers until the write operation completes. * * ```ts - * import { AsyncValue } from "./testutil.ts"; - * import { RwLock } from "./rw_lock.ts"; + * import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; + * import { RwLock } from "https://deno.land/x/async@$MODULE_VERSION/rw_lock.ts"; * * const count = new RwLock(new AsyncValue(0)); * diff --git a/semaphore.ts b/semaphore.ts index 78a7baa..8f66500 100644 --- a/semaphore.ts +++ b/semaphore.ts @@ -4,7 +4,7 @@ import { Notify } from "./notify.ts"; * A semaphore that allows a limited number of concurrent executions of an operation. * * ```ts - * import { Semaphore } from "./semaphore.ts"; + * import { Semaphore } from "https://deno.land/x/async@$MODULE_VERSION/semaphore.ts"; * * const sem = new Semaphore(5); * const worker = () => { diff --git a/stack.ts b/stack.ts index 96dc705..41c86d9 100644 --- a/stack.ts +++ b/stack.ts @@ -6,7 +6,7 @@ import { Notify, WaitOptions } from "./notify.ts"; * * ```ts * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; - * import { Stack } from "./stack.ts"; + * import { Stack } from "https://deno.land/x/async@$MODULE_VERSION/stack.ts"; * * const stack = new Stack(); * stack.push(1); diff --git a/testutil.ts b/testutil.ts index 90577de..1815b8b 100644 --- a/testutil.ts +++ b/testutil.ts @@ -3,7 +3,7 @@ * * ```ts * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; - * import { AsyncValue } from "./testutil.ts"; + * import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; * * const v = new AsyncValue(0); * assertEquals(await v.get(), 0); From 1b00a934a70988e335f5405293a3d9721de5ceb6 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 7 Jan 2024 06:02:21 +0900 Subject: [PATCH 3/6] :package: bump deno.land/std from 0.186.0 to 0.211.0 --- barrier_test.ts | 2 +- lock_test.ts | 2 +- mutex.ts | 2 +- mutex_test.ts | 2 +- notify.ts | 2 +- notify_test.ts | 4 ++-- queue_test.ts | 4 ++-- rw_lock_test.ts | 4 ++-- semaphore_test.ts | 2 +- stack_test.ts | 4 ++-- state_test.ts | 4 ++-- testutil_test.ts | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/barrier_test.ts b/barrier_test.ts index 66ccb5a..61cd522 100644 --- a/barrier_test.ts +++ b/barrier_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { Barrier } from "./barrier.ts"; Deno.test("Barrier", async (t) => { diff --git a/lock_test.ts b/lock_test.ts index d83341c..9399b08 100644 --- a/lock_test.ts +++ b/lock_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { AsyncValue } from "./testutil.ts"; import { Lock } from "./lock.ts"; diff --git a/mutex.ts b/mutex.ts index e78d774..88da6b5 100644 --- a/mutex.ts +++ b/mutex.ts @@ -1,7 +1,7 @@ import { Deferred, deferred, -} from "https://deno.land/std@0.186.0/async/deferred.ts"; +} from "https://deno.land/std@0.211.0/async/deferred.ts"; /** * A mutex (mutual exclusion) is a synchronization primitive that grants diff --git a/mutex_test.ts b/mutex_test.ts index 6d9131b..45eed1f 100644 --- a/mutex_test.ts +++ b/mutex_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { AsyncValue } from "./testutil.ts"; import { Mutex } from "./mutex.ts"; diff --git a/notify.ts b/notify.ts index c6f6f66..ab03c88 100644 --- a/notify.ts +++ b/notify.ts @@ -1,7 +1,7 @@ import { Deferred, deferred, -} from "https://deno.land/std@0.186.0/async/deferred.ts"; +} from "https://deno.land/std@0.211.0/async/deferred.ts"; export type WaitOptions = { signal?: AbortSignal; diff --git a/notify_test.ts b/notify_test.ts index ece06ec..d954487 100644 --- a/notify_test.ts +++ b/notify_test.ts @@ -1,8 +1,8 @@ -import { delay } from "https://deno.land/std@0.186.0/async/delay.ts"; +import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.186.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { promiseState } from "./state.ts"; import { Notify } from "./notify.ts"; diff --git a/queue_test.ts b/queue_test.ts index 521fcf1..99c0d6e 100644 --- a/queue_test.ts +++ b/queue_test.ts @@ -1,8 +1,8 @@ -import { delay } from "https://deno.land/std@0.186.0/async/delay.ts"; +import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.186.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { promiseState } from "./state.ts"; import { Queue } from "./queue.ts"; diff --git a/rw_lock_test.ts b/rw_lock_test.ts index cd84184..df0c9cc 100644 --- a/rw_lock_test.ts +++ b/rw_lock_test.ts @@ -1,5 +1,5 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { deferred } from "https://deno.land/std@0.186.0/async/deferred.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { deferred } from "https://deno.land/std@0.211.0/async/deferred.ts"; import { promiseState } from "./state.ts"; import { AsyncValue } from "./testutil.ts"; import { RwLock } from "./rw_lock.ts"; diff --git a/semaphore_test.ts b/semaphore_test.ts index c3b40cd..707910a 100644 --- a/semaphore_test.ts +++ b/semaphore_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { Semaphore } from "./semaphore.ts"; Deno.test("Semaphore", async (t) => { diff --git a/stack_test.ts b/stack_test.ts index a86534a..a19029e 100644 --- a/stack_test.ts +++ b/stack_test.ts @@ -1,8 +1,8 @@ -import { delay } from "https://deno.land/std@0.186.0/async/delay.ts"; +import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.186.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { promiseState } from "./state.ts"; import { Stack } from "./stack.ts"; diff --git a/state_test.ts b/state_test.ts index 847182a..3ef530c 100644 --- a/state_test.ts +++ b/state_test.ts @@ -1,5 +1,5 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; -import { deferred } from "https://deno.land/std@0.186.0/async/deferred.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { deferred } from "https://deno.land/std@0.211.0/async/deferred.ts"; import { promiseState } from "./state.ts"; Deno.test( diff --git a/testutil_test.ts b/testutil_test.ts index b1e2d92..cf72c2e 100644 --- a/testutil_test.ts +++ b/testutil_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; import { AsyncValue } from "./testutil.ts"; Deno.test("AsyncValue", async (t) => { From 1a2d26f008c3508b428667aac6a1dc84aedf0366 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 7 Jan 2024 06:10:36 +0900 Subject: [PATCH 4/6] Follow the latest `deno_std` changes --- README.md | 8 ++++---- barrier_test.ts | 2 +- lock_test.ts | 2 +- mutex.ts | 12 ++++-------- mutex_test.ts | 2 +- notify.ts | 21 ++++++++++----------- notify_test.ts | 2 +- queue.ts | 2 +- queue_test.ts | 2 +- rw_lock_test.ts | 15 +++++++-------- semaphore_test.ts | 2 +- stack.ts | 2 +- stack_test.ts | 2 +- state_test.ts | 9 ++++----- testutil.ts | 2 +- testutil_test.ts | 2 +- 16 files changed, 40 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 2d16e34..97be49c 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ try { notification. ```ts -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "https://deno.land/x/async@$MODULE_VERSION/state.ts"; import { Notify } from "https://deno.land/x/async@$MODULE_VERSION/notify.ts"; @@ -133,7 +133,7 @@ assertEquals(await promiseState(waiter2), "fulfilled"); with optional waiting when popping elements from an empty queue. ```ts -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { Queue } from "https://deno.land/x/async@$MODULE_VERSION/queue.ts"; const queue = new Queue(); @@ -149,7 +149,7 @@ assertEquals(await queue.pop(), 3); with optional waiting when popping elements from an empty stack. ```ts -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { Stack } from "https://deno.land/x/async@$MODULE_VERSION/stack.ts"; const stack = new Stack(); @@ -202,7 +202,7 @@ console.log(await promiseState(p3)); // pending asynchronously. ```ts -import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; const v = new AsyncValue(0); diff --git a/barrier_test.ts b/barrier_test.ts index 61cd522..c02c4f5 100644 --- a/barrier_test.ts +++ b/barrier_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { Barrier } from "./barrier.ts"; Deno.test("Barrier", async (t) => { diff --git a/lock_test.ts b/lock_test.ts index 9399b08..5734e46 100644 --- a/lock_test.ts +++ b/lock_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { AsyncValue } from "./testutil.ts"; import { Lock } from "./lock.ts"; diff --git a/mutex.ts b/mutex.ts index 88da6b5..f6e48ff 100644 --- a/mutex.ts +++ b/mutex.ts @@ -1,8 +1,3 @@ -import { - Deferred, - deferred, -} from "https://deno.land/std@0.211.0/async/deferred.ts"; - /** * A mutex (mutual exclusion) is a synchronization primitive that grants * exclusive access to a shared resource. @@ -32,7 +27,7 @@ import { * ``` */ export class Mutex { - #waiters: Deferred[] = []; + #waiters: { promise: Promise; resolve: () => void }[] = []; /** * Returns true if the mutex is locked, false otherwise. @@ -47,9 +42,10 @@ export class Mutex { */ async acquire(): Promise { const waiters = [...this.#waiters]; - this.#waiters.push(deferred()); + const { promise, resolve } = Promise.withResolvers(); + this.#waiters.push({ promise, resolve }); if (waiters.length) { - await Promise.all(waiters); + await Promise.all(waiters.map(({ promise }) => promise)); } } diff --git a/mutex_test.ts b/mutex_test.ts index 45eed1f..7293599 100644 --- a/mutex_test.ts +++ b/mutex_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { AsyncValue } from "./testutil.ts"; import { Mutex } from "./mutex.ts"; diff --git a/notify.ts b/notify.ts index ab03c88..dfe81cd 100644 --- a/notify.ts +++ b/notify.ts @@ -1,17 +1,12 @@ -import { - Deferred, - deferred, -} from "https://deno.land/std@0.211.0/async/deferred.ts"; - -export type WaitOptions = { +export interface WaitOptions { signal?: AbortSignal; -}; +} /** * Async notifier that allows one or more "waiters" to wait for a notification. * * ```ts - * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; + * import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; * import { promiseState } from "https://deno.land/x/async@$MODULE_VERSION/state.ts"; * import { Notify } from "https://deno.land/x/async@$MODULE_VERSION/notify.ts"; * @@ -27,7 +22,11 @@ export type WaitOptions = { * ``` */ export class Notify { - #waiters: Deferred[] = []; + #waiters: { + promise: Promise; + resolve: () => void; + reject: (reason?: unknown) => void; + }[] = []; /** * Returns the number of waiters that are waiting for notification. @@ -70,14 +69,14 @@ export class Notify { if (signal?.aborted) { throw new DOMException("Aborted", "AbortError"); } - const waiter = deferred(); + const waiter = Promise.withResolvers(); const abort = () => { removeItem(this.#waiters, waiter); waiter.reject(new DOMException("Aborted", "AbortError")); }; signal?.addEventListener("abort", abort, { once: true }); this.#waiters.push(waiter); - await waiter; + await waiter.promise; signal?.removeEventListener("abort", abort); } } diff --git a/notify_test.ts b/notify_test.ts index d954487..960d1c0 100644 --- a/notify_test.ts +++ b/notify_test.ts @@ -2,7 +2,7 @@ import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.211.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "./state.ts"; import { Notify } from "./notify.ts"; diff --git a/queue.ts b/queue.ts index 36c10f8..e37c173 100644 --- a/queue.ts +++ b/queue.ts @@ -5,7 +5,7 @@ import { Notify, WaitOptions } from "./notify.ts"; * popping elements from an empty queue. * * ```ts - * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; + * import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; * import { Queue } from "https://deno.land/x/async@$MODULE_VERSION/queue.ts"; * * const queue = new Queue(); diff --git a/queue_test.ts b/queue_test.ts index 99c0d6e..aa0f661 100644 --- a/queue_test.ts +++ b/queue_test.ts @@ -2,7 +2,7 @@ import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.211.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "./state.ts"; import { Queue } from "./queue.ts"; diff --git a/rw_lock_test.ts b/rw_lock_test.ts index df0c9cc..e05daba 100644 --- a/rw_lock_test.ts +++ b/rw_lock_test.ts @@ -1,5 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; -import { deferred } from "https://deno.land/std@0.211.0/async/deferred.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "./state.ts"; import { AsyncValue } from "./testutil.ts"; import { RwLock } from "./rw_lock.ts"; @@ -77,7 +76,7 @@ Deno.test("RwLock", async (t) => { "'lock' should block until all readers are done", async () => { const count = new RwLock(new AsyncValue(0)); - const waiter = deferred(); + const { promise, resolve } = Promise.withResolvers(); const writer = () => { return count.lock(() => { // Do nothing @@ -85,14 +84,14 @@ Deno.test("RwLock", async (t) => { }; const reader = () => { return count.rlock(async () => { - await waiter; + await promise; }); }; const r = reader(); const w = writer(); assertEquals(await promiseState(r), "pending"); assertEquals(await promiseState(w), "pending"); - waiter.resolve(); + resolve(); assertEquals(await promiseState(r), "fulfilled"); assertEquals(await promiseState(w), "fulfilled"); }, @@ -102,10 +101,10 @@ Deno.test("RwLock", async (t) => { "'rlock' should block until all writers are done", async () => { const count = new RwLock(new AsyncValue(0)); - const waiter = deferred(); + const { promise, resolve } = Promise.withResolvers(); const writer = () => { return count.lock(async () => { - await waiter; + await promise; }); }; const reader = () => { @@ -117,7 +116,7 @@ Deno.test("RwLock", async (t) => { const r = reader(); assertEquals(await promiseState(w), "pending"); assertEquals(await promiseState(r), "pending"); - waiter.resolve(); + resolve(); assertEquals(await promiseState(w), "fulfilled"); assertEquals(await promiseState(r), "fulfilled"); }, diff --git a/semaphore_test.ts b/semaphore_test.ts index 707910a..8a22950 100644 --- a/semaphore_test.ts +++ b/semaphore_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { Semaphore } from "./semaphore.ts"; Deno.test("Semaphore", async (t) => { diff --git a/stack.ts b/stack.ts index 41c86d9..bbb6c7e 100644 --- a/stack.ts +++ b/stack.ts @@ -5,7 +5,7 @@ import { Notify, WaitOptions } from "./notify.ts"; * popping elements from an empty stack. * * ```ts - * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; + * import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; * import { Stack } from "https://deno.land/x/async@$MODULE_VERSION/stack.ts"; * * const stack = new Stack(); diff --git a/stack_test.ts b/stack_test.ts index a19029e..58eebe5 100644 --- a/stack_test.ts +++ b/stack_test.ts @@ -2,7 +2,7 @@ import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; import { assertEquals, assertRejects, -} from "https://deno.land/std@0.211.0/testing/asserts.ts"; +} from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "./state.ts"; import { Stack } from "./stack.ts"; diff --git a/state_test.ts b/state_test.ts index 3ef530c..d391932 100644 --- a/state_test.ts +++ b/state_test.ts @@ -1,5 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; -import { deferred } from "https://deno.land/std@0.211.0/async/deferred.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { promiseState } from "./state.ts"; Deno.test( @@ -28,11 +27,11 @@ Deno.test( ); Deno.test("promiseState() returns refreshed status", async () => { - const d = deferred(); + const { promise, resolve } = Promise.withResolvers(); const p = (async () => { - await d; + await promise; })(); assertEquals(await promiseState(p), "pending"); - d.resolve(); + resolve(); assertEquals(await promiseState(p), "fulfilled"); }); diff --git a/testutil.ts b/testutil.ts index 1815b8b..6252f21 100644 --- a/testutil.ts +++ b/testutil.ts @@ -2,7 +2,7 @@ * A class that wraps a value and allows it to be set asynchronously. * * ```ts - * import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts"; + * import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; * import { AsyncValue } from "https://deno.land/x/async@$MODULE_VERSION/testutil.ts"; * * const v = new AsyncValue(0); diff --git a/testutil_test.ts b/testutil_test.ts index cf72c2e..3321dde 100644 --- a/testutil_test.ts +++ b/testutil_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.211.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { AsyncValue } from "./testutil.ts"; Deno.test("AsyncValue", async (t) => { From 5f1d89666929e6192e0a776004dab0a7de6af9e7 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 7 Jan 2024 06:13:30 +0900 Subject: [PATCH 5/6] Improve internal logic of notify --- notify.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/notify.ts b/notify.ts index dfe81cd..d248a89 100644 --- a/notify.ts +++ b/notify.ts @@ -40,20 +40,22 @@ export class Notify { * If there are fewer than `n` waiters, all waiters are notified. */ notify(n = 1): void { - for (const _ of Array(n)) { - const waiter = this.#waiters.shift(); - if (!waiter) { - break; - } + const head = this.#waiters.slice(0, n); + const tail = this.#waiters.slice(n); + for (const waiter of head) { waiter.resolve(); } + this.#waiters = tail; } /** * Notifies all waiters that are waiting for notification. Resolves each of the notified waiters. */ notifyAll(): void { - this.notify(this.#waiters.length); + for (const waiter of this.#waiters) { + waiter.resolve(); + } + this.#waiters = []; } /** From 1b3a8974ee2b479dbb1aa3cfc43ee9a1dac8cef1 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 7 Jan 2024 06:44:11 +0900 Subject: [PATCH 6/6] Add `WaitGroup` like in Go --- README.md | 26 +++++++++++++++++++++ mod.ts | 1 + wait_group.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++++ wait_group_test.ts | 36 ++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 wait_group.ts create mode 100644 wait_group_test.ts diff --git a/README.md b/README.md index 97be49c..fee12ab 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,32 @@ worker(2); worker(3); ``` +### WaitGroup + +`WaitGroup` is a synchronization primitive that enables promises to coordinate +and synchronize their execution. It is particularly useful in scenarios where a +specific number of tasks must complete before the program can proceed. + +```ts +import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; +import { WaitGroup } from "https://deno.land/x/async@$MODULE_VERSION/wait_group.ts"; + +const wg = new WaitGroup(); + +async function worker(id: number) { + wg.add(1); + console.log(`worker ${id} is waiting`); + await delay(100); + console.log(`worker ${id} is done`); + wg.done(); +} + +worker(1); +worker(2); +worker(3); +await wg.wait(); +``` + ### Lock/RwLock `Lock` is a mutual exclusion lock that provides safe concurrent access to a diff --git a/mod.ts b/mod.ts index 9b4a823..1c5eadd 100644 --- a/mod.ts +++ b/mod.ts @@ -8,3 +8,4 @@ export * from "./semaphore.ts"; export * from "./stack.ts"; export * from "./state.ts"; export * from "./testutil.ts"; +export * from "./wait_group.ts"; diff --git a/wait_group.ts b/wait_group.ts new file mode 100644 index 0000000..c4bc4c9 --- /dev/null +++ b/wait_group.ts @@ -0,0 +1,58 @@ +import { Notify } from "./notify.ts"; + +/** + * `WaitGroup` is a synchronization primitive that enables promises to coordinate + * and synchronize their execution. It is particularly useful in scenarios where + * a specific number of tasks must complete before the program can proceed. + * + * ```ts + * import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; + * import { WaitGroup } from "https://deno.land/x/async@$MODULE_VERSION/wait_group.ts"; + * + * const wg = new WaitGroup(); + * + * async function worker(id: number) { + * wg.add(1); + * console.log(`worker ${id} is waiting`); + * await delay(100); + * console.log(`worker ${id} is done`); + * wg.done(); + * } + * + * worker(1); + * worker(2); + * worker(3); + * await wg.wait(); + * ``` + */ +export class WaitGroup { + #notify = new Notify(); + #count = 0; + + /** + * Adds the specified `delta` to the WaitGroup counter. If the counter becomes + * zero, it signals all waiting promises to proceed. + * @param delta The number to add to the counter. It can be positive or negative. + */ + add(delta: number): void { + this.#count += delta; + if (this.#count === 0) { + this.#notify.notifyAll(); + } + } + + /** + * Decrements the WaitGroup counter by 1, equivalent to calling `add(-1)`. + */ + done(): void { + this.add(-1); + } + + /** + * Returns a promise that waits for the WaitGroup counter to reach zero. + * @returns A Promise that resolves when the counter becomes zero. + */ + wait(): Promise { + return this.#notify.notified(); + } +} diff --git a/wait_group_test.ts b/wait_group_test.ts new file mode 100644 index 0000000..51966f7 --- /dev/null +++ b/wait_group_test.ts @@ -0,0 +1,36 @@ +import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; +import { delay } from "https://deno.land/std@0.211.0/async/delay.ts"; +import { WaitGroup } from "./wait_group.ts"; + +Deno.test("WaitGroup", async (t) => { + await t.step( + "Ensure WaitGroup synchronizes multiple workers", + async () => { + const wg = new WaitGroup(); + const workers = []; + const results: string[] = []; + for (let i = 0; i < 5; i++) { + workers.push((async () => { + wg.add(1); + results.push(`before wait ${i}`); + await delay(100); + results.push(`after wait ${i}`); + wg.done(); + })()); + } + await wg.wait(); + assertEquals(results, [ + "before wait 0", + "before wait 1", + "before wait 2", + "before wait 3", + "before wait 4", + "after wait 0", + "after wait 1", + "after wait 2", + "after wait 3", + "after wait 4", + ]); + }, + ); +});