Pool.get is not first-come-first-served: the releasing fiber re-acquires before fibers already waiting
I maintainer di solito rispondono entro 1 giorno
Una pull request collegata è già stata integrata.
- #10026 di @tim-smart — integrata
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- typescript
Direzione di ricerca
Start with packages/effect/src/Pool.ts, especially get, waitForItem, addAvailableFront, and wakeWaiters; trace how a released item is made available and how queued fibers are resumed. The reproduction in the issue provides the failing FIFO expectation; run it against the pool implementation and check that waiting requests are admitted in arrival order without repeated overtaking. The report also notes similar behavior in src/Semaphore.ts, so assess whether the fix needs to cover both.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
What version of Effect is running?
effect 4.0.6 (also 4.0.5; src/Pool.ts is identical in both). The same behaviour reaches @effect/sql-pg, whose pool is built on Pool.
What steps can reproduce the bug?
-
Create an empty folder with this
package.json:{ "name": "effect-pool-fairness-repro", "private": true, "type": "module", "devDependencies": { "effect": "4.0.6", "@effect/vitest": "4.0.6", "vitest": "5.0.5" } } -
Add
pool.test.ts:import { expect, it } from "@effect/vitest" import { Effect, Fiber, Pool } from "effect" import { TestClock } from "effect/testing" it.effect("a later request is admitted before the ones already waiting", () => Effect.gen(function*() { const pool = yield* Pool.make({ acquire: Effect.succeed("connection"), size: 1 }) let asked = 0 const admitted: Array<number> = [] const use = Effect.scoped(Effect.gen(function*() { const ticket = asked++ // ticket = arrival order of the request yield* Pool.get(pool) admitted.push(ticket) // record the order in which requests get the item yield* Effect.sleep("10 millis") })) // One fiber uses the item three times in a row (asking at 0, 10 and 20 ms); // three other fibers start asking at 1, 2 and 3 ms. const chained = yield* Effect.forkChild(Effect.repeat(use, { times: 2 })) const others = yield* Effect.forEach([1, 2, 3], (at) => Effect.forkChild(Effect.delay(use, `${at} millis`))) yield* TestClock.adjust("100 millis") yield* Fiber.join(chained) yield* Fiber.joinAll(others) expect(admitted).toEqual([0, 1, 2, 3, 4, 5]) }).pipe(Effect.scoped)) -
Run
npm installandnpx vitest run pool.test.ts(Bun works the same).
What is the expected behavior?
Requests are admitted in arrival order: [0, 1, 2, 3, 4, 5]. Requests 1, 2 and 3 started waiting at 1-3 ms, before the chained fiber asked again at 10 and 20 ms (tickets 4 and 5).
What do you see instead?
AssertionError: expected [ 0, 4, 5, 3, 1, 2 ] to deeply equal [ 0, 1, 2, 3, 4, 5 ]
It fails on every run. The fiber that releases the item gets it straight back (0, 4, 5), and the fibers that were already waiting are admitted last, and not in their own arrival order either (3, then 1, then 2).
Additional information
Where it comes from, in packages/effect/src/Pool.ts at 4.0.6:
get(L462-473) leasesstate.availableHeadwhenever an item is available, without checking whetherstate.waitersis empty.- On release, the item goes back to the front (
addAvailableFront, L709), andwakeWaiters(L710, L737-751) wakes waiters in a task scheduled for later (currentDispatcher.scheduleTask(..., 0)). - Between the release and that task, any fiber that calls
getsynchronously takes the item. Usually that is the fiber that just released it and is running its next statement. - A woken waiter that loses that race goes back through
waitForItem(L721-735), which re-adds it at the end of thewaitersset. That explains the reversed order among the waiters.
Semaphore has the same shape: take succeeds when free >= n regardless of waiters (src/Semaphore.ts L223), and releaseUnsafe (L267) wakes observers that re-check later. So putting a semaphore in front of the pool does not make it fair.
Impact we measured (@effect/sql-pg pool of 4 connections, about 50 concurrent fibers each running a few consecutive statements):
- The median acquisition waits 0 ms, because the releasing fiber keeps the connection.
- p99 is 1.7 s and the worst case 4.2 s.
- Among acquisitions that waited more than 1 s, a median of 146 later acquisitions were admitted first.
In a micro-benchmark of that pattern (50 fibers × 5 calls, 4 connections), the worst wait per call was about 1.2 s, with up to about 900 overtakes per acquisition. With a first-come gate in front of the pool it drops to about 0.27 s, with at most 1 overtake.
Possible direction: hand a released item (or permit) directly to the oldest waiter, instead of putting it back and waking waiters later, and let get/take use an available item only when nobody is waiting. Keeping waiters in FIFO order, and not re-queueing a woken waiter at the back, would also bound the wait. If the current behaviour is intentional for throughput (the releasing fiber keeps a warm connection), an opt-in fairness: "fifo" option on Pool.make and the SQL pools would cover latency-sensitive callers.
- Lingua principale
- TypeScript
- Stelle
- 16.7k
- Fork
- 808
- Merge medio
- 11h 33m
- PR unite (30g)
- 855
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Effect-TS/effect
-
`fromJsonSchemaDocument`: import open `patternProperties`Forse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10048 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
`fromJsonSchemaDocument`: intersect an object with `anyOf` branches that re-declare an enum propertyAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 42/100
I maintainer di solito rispondono entro 1 giorno
-
`fromJsonSchemaDocument`: import `dependentRequired` (draft-07 `dependencies`)Forse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10046 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
`fromJsonSchemaDocument`: import `if` / `then` / `else`Forse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10045 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
`fromJsonSchemaDocument`: import the `not` keywordForse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10044 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Effect-TS/effect
Issue simili
-
[Bug]: Pen taps on sidebar rows start a drag and lose their clickForse già presa @makhnatkin l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
b310-digital/teammapper#1515 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
puckeditor/puck#1884 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
outline/outline#14378 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
nicobailon/pi-web-access#642 ·
I maintainer di solito rispondono entro 1 giorno