Skip to content

Commit d2760e9

Browse files
tim-smarteffect-bot
authored andcommitted
add Effect.Semaphore.resize (#5295)
1 parent 1a21c6f commit d2760e9

File tree

4 files changed

+62
-22
lines changed

4 files changed

+62
-22
lines changed

.changeset/icy-walls-make.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
add Effect.Semaphore.resize

packages/effect/src/Effect.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11770,6 +11770,11 @@ export interface Permit {
1177011770
* @since 2.0.0
1177111771
*/
1177211772
export interface Semaphore {
11773+
/**
11774+
* Adjusts the number of permits available in the semaphore.
11775+
*/
11776+
resize(permits: number): Effect<void>
11777+
1177311778
/**
1177411779
* Runs an effect with the given number of permits and releases the permits
1177511780
* when the effect completes.

packages/effect/src/internal/effect/circular.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Semaphore {
3737
public waiters = new Set<() => void>()
3838
public taken = 0
3939

40-
constructor(readonly permits: number) {}
40+
constructor(public permits: number) {}
4141

4242
get free() {
4343
return this.permits - this.taken
@@ -63,21 +63,35 @@ class Semaphore {
6363
return resume(core.succeed(n))
6464
})
6565

66-
readonly updateTaken = (f: (n: number) => number): Effect.Effect<number> =>
67-
core.withFiberRuntime((fiber) => {
68-
this.taken = f(this.taken)
69-
if (this.waiters.size > 0) {
70-
fiber.getFiberRef(currentScheduler).scheduleTask(() => {
71-
const iter = this.waiters.values()
72-
let item = iter.next()
73-
while (item.done === false && this.free > 0) {
74-
item.value()
75-
item = iter.next()
76-
}
77-
}, fiber.getFiberRef(core.currentSchedulingPriority))
78-
}
79-
return core.succeed(this.free)
80-
})
66+
updateTakenUnsafe(fiber: Fiber.RuntimeFiber<any, any>, f: (n: number) => number): Effect.Effect<number> {
67+
this.taken = f(this.taken)
68+
if (this.waiters.size > 0) {
69+
fiber.getFiberRef(currentScheduler).scheduleTask(() => {
70+
const iter = this.waiters.values()
71+
let item = iter.next()
72+
while (item.done === false && this.free > 0) {
73+
item.value()
74+
item = iter.next()
75+
}
76+
}, fiber.getFiberRef(core.currentSchedulingPriority))
77+
}
78+
return core.succeed(this.free)
79+
}
80+
81+
updateTaken(f: (n: number) => number): Effect.Effect<number> {
82+
return core.withFiberRuntime((fiber) => this.updateTakenUnsafe(fiber, f))
83+
}
84+
85+
readonly resize = (permits: number) =>
86+
core.asVoid(
87+
core.withFiberRuntime((fiber) => {
88+
this.permits = permits
89+
if (this.free < 0) {
90+
return core.void
91+
}
92+
return this.updateTakenUnsafe(fiber, (taken) => taken)
93+
})
94+
)
8195

8296
readonly release = (n: number): Effect.Effect<number> => this.updateTaken((taken) => taken - n)
8397

@@ -104,7 +118,7 @@ class Semaphore {
104118
}
105119

106120
/** @internal */
107-
export const unsafeMakeSemaphore = (permits: number): Semaphore => new Semaphore(permits)
121+
export const unsafeMakeSemaphore = (permits: number): Effect.Semaphore => new Semaphore(permits)
108122

109123
/** @internal */
110124
export const makeSemaphore = (permits: number) => core.sync(() => unsafeMakeSemaphore(permits))

packages/effect/test/Effect/semaphore.test.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it } from "@effect/vitest"
1+
import { assert, describe, it } from "@effect/vitest"
22
import { strictEqual } from "@effect/vitest/utils"
33
import * as D from "effect/Duration"
44
import * as Effect from "effect/Effect"
@@ -35,9 +35,25 @@ describe("Effect", () => {
3535

3636
it.effect("releaseAll", () =>
3737
Effect.gen(function*() {
38-
const sem = yield* (Effect.makeSemaphore(4))
39-
yield* (sem.take(4))
40-
yield* (sem.releaseAll)
41-
yield* (sem.take(1))
38+
const sem = yield* Effect.makeSemaphore(4)
39+
yield* sem.take(4)
40+
yield* sem.releaseAll
41+
yield* sem.take(1)
42+
}))
43+
44+
it.effect("resize", () =>
45+
Effect.gen(function*() {
46+
const sem = yield* Effect.makeSemaphore(4)
47+
yield* sem.take(4)
48+
yield* sem.resize(2)
49+
const fiber = yield* Effect.fork(sem.take(1))
50+
yield* TestClock.adjust(1)
51+
assert.isNull(fiber.unsafePoll())
52+
yield* sem.release(2)
53+
yield* TestClock.adjust(1)
54+
assert.isNull(fiber.unsafePoll())
55+
yield* sem.release(1)
56+
yield* TestClock.adjust(1)
57+
assert.isTrue(fiber.unsafePoll() !== null)
4258
}))
4359
})

0 commit comments

Comments
 (0)