Repository navigation
Description
What version of Effect is running?
4.0.6 (also present on main: packages/effect/src/Pool.ts, wakeWaiters)
What steps can reproduce the bug?
When a pool item is released, wakeWaiters defers the wake-up with fiber.currentDispatcher.scheduleTask(..., 0), but the item is already marked available. A Pool.get that runs before that scheduled task (for example, the releasing fiber borrowing again right away) takes the item ahead of fibers that were already queued.
import { Effect, Exit, Fiber, Pool, Scope } from "effect"
const program = Effect.scoped(
Effect.gen(function* () {
const pool = yield* Pool.make({ acquire: Effect.succeed("connection"), size: 1 })
const holder = yield* Scope.make()
yield* Pool.get(pool).pipe(Scope.provide(holder))
const order: string[] = []
const borrow = (name: string) =>
Effect.scoped(Effect.gen(function* () {
yield* Pool.get(pool)
order.push(name)
}))
const first = yield* Effect.forkChild(borrow("first"), { startImmediately: true })
const second = yield* Effect.forkChild(borrow("second"), { startImmediately: true })
yield* Scope.close(holder, Exit.void) // release the only item
yield* borrow("newcomer") // borrow again right away
yield* Fiber.join(first)
yield* Fiber.join(second)
return order
})
)
Effect.runPromise(program).then(console.log)What is the expected behavior?
["first", "second", "newcomer"]: a released item goes to the oldest queued waiter before any new get can take it.
What do you see instead?
["newcomer", "first", "second"].
Additional information
We hit this with an @effect/sql-pg connection pool under load. Busy fibers that release and immediately re-borrow keep getting connections, while queries already waiting in the queue starve until they hit their acquire timeout. Older work times out even though the pool keeps serving newer work.
A possible fix is to hand the released item to the oldest waiter synchronously during release (reserving it for that waiter), instead of publishing it as available and waking waiters on a later tick. The handoff also has to return the reservation if the waiter is interrupted. We have a local patch along these lines with tests for FIFO order across get/use, interruption during handoff, concurrency > 1, and invalidation, and can share it if that helps.
Activity
effect-bot commentedon Oct 24, 2026
Thanks for the detailed report and repro. This is already fixed on
mainby #9800, which merged shortly before this issue was opened. It hasn't been released yet: 4.0.6 is still the latest release, so the fix will go out in the next one.I turned your repro into a test and ran it with
Pool.getandPool.use, atconcurrency1 and 2:main, every case gives["first", "second", "newcomer"].newcomerfirst, which matches what you saw.#9800 doesn't reserve the released item for the oldest waiter as you suggested, but it gives the same FIFO guarantee:
get/useskips the fast path and queues behind the existing waiters.#9800 also added tests for the other cases you mentioned: a busy fiber that releases and re-borrows, interruption after a wake,
concurrency > 1with reservations, and a waiter woken during its own interruption.If you can run your local patch's tests, or your
@effect/sql-pgworkload, against a snapshot ofmain, please let us know whether anything still fails.