Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

v4 Cache.get: a caller that joins an abandoned lookup fails with an interruption it did not ask for

Abierto
#9,693 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
42/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
typescript
Área
api, backend

Línea de trabajo

The report points to Cache.get and EntryImpl.await; start by tracing how callers join an entry, how awaiters is updated, and how the lookup fiber or Deferred completes. Reproduce the timing in the issue’s TypeScript example, then compare the behavior on the v4 branch and main. Done means a caller that did not leave receives the lookup’s result or its own failure, not another caller’s interruption.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Version: effect 4.0.0-beta.107 (the v4 line developed in Effect-TS/effect-smol, which does not take issues from outside the org).

Cache.get can fail a caller with an interrupt-only cause even though nothing interrupted that caller. It happens when the caller joins a lookup that another caller abandoned.

Reproduction (effect 4.0.0-beta.107, Bun 1.4.3)
import { Cache, Cause, Effect, Exit, Fiber } from "effect"

const program = Effect.gen(function* () {
  let lookups = 0
  const cache = yield* Cache.make({
    capacity: 10,
    // A lookup that cannot stop at once, like a spawned process or a blocked query
    lookup: (_key: string) =>
      Effect.sync(() => ++lookups).pipe(
        Effect.andThen(Effect.sleep("300 millis").pipe(Effect.uninterruptible)),
        Effect.as("value")
      )
  })
  const a = yield* Cache.get(cache, "k").pipe(Effect.forkChild)
  yield* Effect.sleep("50 millis")
  yield* Fiber.interrupt(a).pipe(Effect.forkChild) // the only waiting caller gives up
  yield* Effect.sleep("50 millis")
  const b = yield* Effect.exit(Cache.get(cache, "k")) // b is never interrupted
  console.log(Exit.isFailure(b) && Cause.hasInterruptsOnly(b.cause)) // true
  const c = yield* Effect.exit(Cache.get(cache, "k"))
  console.log(Exit.isSuccess(c), lookups) // true 2
})

Effect.runPromise(program)
What happens

When the last caller waiting on an entry leaves, the cache interrupts the lookup fiber (in 4.0.0-beta.107, EntryImpl.await calls fiberInterrupt once awaiters reaches 0). A caller that calls get before that fiber has exited finds the entry still in the map. It joins the fiber and receives its interrupted exit. Any catchCause above that caller then sees an interrupt-only cause, which looks exactly like a cancellation of the caller itself.

On main the lookup runs inside the first caller's fiber (effect.onExit(self.lookup(key), …) completes the shared Deferred with that fiber's exit). Interrupting that first caller would complete the Deferred with its interruption for every joiner. That conclusion comes from reading the code; I have not run it.

Expected

A caller that was not interrupted should get the lookup's value, or a failure of the lookup's own. One way to get there: the lookup is owned by the cache rather than by its first caller, and a caller that leaves stops only its own wait.

Impact

We hit this in a desktop app: a request that joined a repository lookup, which a timed-out snapshot had abandoned, ended interrupted. That stopped the user's first message from starting a session until they sent a second one. Thanks to @dahli4, whose trace from their own machine pinned it down. On our side we now run lookups in a scope the cache's owner holds, and retry a joiner that got an interruption it did not ask for.

Lenguaje dominante
TypeScript
Estrellas
16.7k
Forks
808
Merge medio
10 h 7 min
PR fusionados (30 d)
808

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de Effect-TS/effect

Todos los issues de Effect-TS/effect

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.