Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Use staged persistence effect APIs to restrict unsafe chaining

Aperta
#3,683 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
25/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
java, scala

Direzione di ricerca

Start from the Pekko persistence effect sources: the ReplyEffect, Effect and ReplyEffectWithStop traits in Scala and Java EffectBuilder. Read the #3682 review and the isolated prototype to learn the staged hierarchy. 'Done' means legal chains like reply().thenStop() compile, illegal stash().thenStop() reject, and Scala/Java tests with @since 2.0.0 pass in CI.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Motivation

Adding thenStop directly to ReplyEffect also exposes Effect.stash().thenStop() in both persistence models and both DSLs. This stashes the current command and immediately stops the actor, discarding that command without a reply. Enforced replies do not reject it because stashing already returns a ReplyEffect.

This was identified in the review of #3682. Instead of accepting the combination or throwing when the effect is constructed, we can express the permitted operations through stages in the public API.

Proposed staged API

Use a session-type-inspired staged API (also commonly described as typestate): each returned type exposes only the operations supported at that stage. This is a restriction on effect construction, not a full session type system for actor communication.

For thenStop, introduce ReplyEffectWithStop as a subtype of ReplyEffect. The name describes the added stop capability:

trait ReplyEffect[+Event, State] extends Effect[Event, State] {
  def thenUnstashAll(): ReplyEffect[Event, State]
}

trait ReplyEffectOps[
    +Event, State, +Self <: ReplyEffectOps[Event, State, Self]]
    extends ReplyEffect[Event, State] {
  override def thenUnstashAll(): Self
}

trait ReplyEffectWithStop[+Event, State]
    extends ReplyEffectOps[Event, State, ReplyEffectWithStop[Event, State]] {
  def thenStop(): ReplyEffectWithStop[Event, State]
}
Operation Returned stage
reply, thenReply ReplyEffectWithStop
noReply, thenNoReply ReplyEffectWithStop
stash ReplyEffect

Shared operations use an F-bounded Self, following the Netty AbstractBootstrap pattern. Each stage binds Self once, so it does not need to redeclare every common chaining method. Generic helpers can return Self and preserve the stage.

ReplyEffectOps extends ReplyEffect, retaining both the broad command-handler contract and the specific Self stage in generic helpers. Declaration-only Java Effect, ReplyEffect, and ReplyEffectWithStop do not require an EffectImpl self-type constraint. The existing Java EffectBuilder keeps its constraint because its concrete callback/reply method bodies rely on that implementation type. Scala and Java generic-helper probes pass with this hierarchy.

The names distinguish responsibilities: ReplyEffect is the general reply decision, ReplyEffectOps defines shared stage-preserving operations, and ReplyEffectWithStop adds the stop capability.

The base ReplyEffect must not declare thenStop: a subtrait cannot hide an inherited operation. Scala and Java, and event sourcing and durable state, should follow the same rules. Public traits should contain declarations; concrete effect implementations implement the methods.

Expected behavior and boundaries
  • reply(...).thenStop() and persist(...).thenReply(...).thenStop() compile.
  • reply(...).thenUnstashAll().thenStop() retains the stop capability.
  • stash().thenStop() and stash().thenUnstashAll().thenStop() do not compile.
  • A helper that may stash returns ReplyEffect, so its caller cannot append thenStop. A helper whose callers need that capability must explicitly return ReplyEffectWithStop.
  • noReply deliberately opts out of replying and can still be followed by stopping.
  • This only constrains the current effect chain. Stopping can still discard commands stashed by earlier commands; it cannot prove that the actor's stash is empty or that a recipient has processed a reply.
Compatibility and validation

An isolated prototype using the real effect sources compiled on Scala 2.13 and Java, accepted legal chains and rejected illegal ones. Directly narrowing existing factory return types changes JVM descriptors: a Java client compiled against the old EffectFactories.noReply(): ReplyEffect failed with NoSuchMethodError on that prototype. A separate one-method bridge experiment preserved that call, but introduced compatibility machinery.

For the proposed 2.0.0 change, prefer the direct staged API and explicitly document the intentional binary incompatibility, rather than adding bridge machinery. Any MiMa exceptions must identify the exact changed signatures and the 2.0.0 rationale. Full repository validation remains required; the isolated prototype is not a substitute for CI.

#3682 is being revised as the first concrete application. Once it is merged, a separate thenRun proposal should follow the same stage design, including whether callbacks are supported after stashing and how capability is preserved through callback chaining. Avoid introducing thenRun into #3682.

Acceptance includes positive and negative compilation coverage, runtime reply/stop ordering, Scala/Java parity, documentation, @since 2.0.0, and remote CI including binary compatibility checks with only the agreed intentional changes excluded.

Prior art

jOOQ's DSL API uses an interface hierarchy to express legal construction sequences. Its guidance on step types also highlights the composition and compatibility costs of exposing those types in helper signatures. Those tradeoffs should be explicit in Pekko's design.

Refs #3682.

Lingua principale
Scala
Stelle
1.7k
Fork
212
Merge medio
21h 21m
PR unite (30g)
99

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di apache/pekko

Tutte le issue di apache/pekko

Issue simili

Altre issue su Scala

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.