Skip to content

Pool: a new get can take a released item ahead of queued waiters #9875

Open

Description

@Dawsson

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

  1. effect-bot commented on Oct 24, 2026

    @effect-bot
    Contributor

    Thanks for the detailed report and repro. This is already fixed on main by #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.get and Pool.use, at concurrency 1 and 2:

    #9800 doesn't reserve the released item for the oldest waiter as you suggested, but it gives the same FIFO guarantee:

    • While any waiter is queued, a new get/use skips the fast path and queues behind the existing waiters.
    • A woken waiter keeps its place until it actually takes an item, so a borrower can't overtake it while its wake is still scheduled.
    • If a woken waiter is interrupted before it takes the item, it passes the wake to the next waiter, so no capacity is lost.

    #9800 also added tests for the other cases you mentioned: a busy fiber that releases and re-borrows, interruption after a wake, concurrency > 1 with reservations, and a waiter woken during its own interruption.

    If you can run your local patch's tests, or your @effect/sql-pg workload, against a snapshot of main, please let us know whether anything still fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions