Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions docs/features/message-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,60 @@ Do not write `workflow_messages` rows or cursor rows directly from application
workflow code. The facade is the stable contract; lower-level message services
exist for package/runtime integration.

:::warning Message streams in Workflow 2.20.0 and earlier
:::warning Message streams before Workflow 2.20.3

Workflow code replays on every later workflow task and on every query. In
Workflow releases through 2.20.0, `outbox()->sendReference()` writes a new
outbound and inbound message on each replay, and `inbox()->receive()` and
`receiveOne()` can return a different message, or none, on replay
([workflow#1010](https://github.com/durable-workflow/workflow/issues/1010)).
Workflow 2.20.1 fixes this: a replay returns what the original call sent or
received, and a query never writes or consumes a message.
Workflow 2.20.1 records sends and receives for replay and prevents queries
from writing or consuming messages. Empty receives and streams kept across
steps need the additional replay fixes in 2.20.3, described below.

- Rows that earlier releases duplicated are not removed. Give each logical
send a stable `idempotencyKey`, such as `reply:{turn}`, and deduplicate by
`idempotency_key` when consuming a stream that an earlier release wrote.
Messages that an earlier release consumed under the wrong step stay
consumed.
- In every release, signals and all streams share one message cursor, so a
signal delivered after a pending stream message hides that message
- Signals, updates and all streams share one message cursor. Through 2.20.2,
a signal, update or receive on another stream can move it past pending
messages and hide them
([workflow#1011](https://github.com/durable-workflow/workflow/issues/1011)).
Receive only after a signal or condition says a message is there, and send
that signal before the message.
For the signal-ordering case on those releases, send the signal before the
message and receive only after a signal or condition says a message is there.

:::

## Stream upgrades and rollback {#stream-upgrades}

In Workflow 2.20.3, the shared cursor no longer hides pending messages from
`receive()`, `receiveOne()`, `peek()`, `pendingCount()` or `hasPending()`.
Receives keep sequence order and their batch limit. After upgrading, a receive
loop can return older pending messages that the shared cursor previously hid,
before newer messages.

A replayed receive returns what its original step consumed. If the original
call found nothing, replay returns nothing again. An update handler running
for the first time can still receive. A stream kept in a property now records
receives at the current workflow step, rather than the step where the stream
was opened. Receives that earlier releases recorded at the opening step still
replay.

Before rolling an embedded application back below 2.20.3, follow the
[replay testing gate](../testing.md#replay-before-deployment) with histories
written by the newer package. Earlier versions look up kept-stream receives
at the opening step and can replay those runs differently. Rollback also
restores the shared-cursor, empty-receive replay and fake task-drain precision
bugs. See the [tagged compatibility notes](https://github.com/durable-workflow/workflow/blob/2.20.3/CHANGELOG.md#2203---2026-11-04).

The query limitation in
[workflow#1024](https://github.com/durable-workflow/workflow/issues/1024)
remains: a query can return nothing for a message an update handler received
because it replays the handler at the wrong step. The query changes no message
rows. The workflow's own replay and completed output still return what the
handler received.

## Receiving Messages

Use `peek()` when a workflow needs to inspect pending inbound messages without
Expand Down
8 changes: 8 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ public function testTimerWorkflow(): void

`WorkflowStub::runReadyTasks()` only drains tasks that are already due. It does not fast-forward wall clock time or force future-delayed timers to fire early.

Workflow 2.20.3 drains fake tasks at their exact due time, including fractional
seconds, using the configured task model's persisted timestamp precision.
Earlier versions could skip SQLite tasks due at that instant. A future
fractional deadline stays pending until it is due. Timer delays and retry
backoff still use whole seconds. See the
[stream upgrade and rollback limits](./features/message-streams.md#stream-upgrades)
before rolling an embedded application back to an older package.

### Sending Signals in Fake Mode

Signal-waiting workflows can receive signals in fake mode. When you call `$workflow->signal(...)`, the engine records the durable signal command and creates a workflow task. In fake mode that task executes inline, so the workflow resumes synchronously:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,70 @@ Schreiben Sie aus Workflow-Code der Anwendung keine `workflow_messages`- oder
Cursor-Zeilen direkt. Die Fassade ist der stabile Vertrag. Niedrigere
Nachrichtendienste existieren für die Paket-/Laufzeitintegration.

:::warning Nachrichtenströme in Workflow 2.20.0 und früher
:::warning Nachrichtenströme vor Workflow 2.20.3

Workflow-Code wird bei jeder späteren Workflow-Aufgabe und bei jeder Abfrage
erneut abgespielt (Replay). In Workflow-Releases bis einschließlich 2.20.0
schreibt `outbox()->sendReference()` bei jedem Replay eine neue ausgehende und
eingehende Nachricht, und `inbox()->receive()` und `receiveOne()` können beim
Replay eine andere Nachricht oder gar keine zurückgeben
([workflow#1010](https://github.com/durable-workflow/workflow/issues/1010)).
Workflow 2.20.1 behebt dies: Ein Replay gibt zurück, was der ursprüngliche
Aufruf gesendet oder empfangen hat, und eine Abfrage schreibt oder verbraucht
niemals eine Nachricht.
Workflow 2.20.1 zeichnet Sendevorgänge und Empfangsvorgänge für das Replay auf
und verhindert, dass Abfragen Nachrichten schreiben oder verbrauchen. Leere
Empfangsaufrufe und über Schritte hinweg aufbewahrte Ströme benötigen die
weiteren Replay-Korrekturen aus 2.20.3, die unten beschrieben werden.

- Zeilen, die frühere Releases dupliziert haben, werden nicht entfernt. Geben
Sie jedem logischen Sendevorgang einen stabilen `idempotencyKey`, etwa
`reply:{turn}`, und entfernen Sie Duplikate anhand von `idempotency_key`,
wenn Sie einen Strom verarbeiten, den ein früheres Release geschrieben hat.
Nachrichten, die ein früheres Release unter dem falschen Schritt verbraucht
hat, bleiben verbraucht.
- In jedem Release teilen sich Signale und alle Ströme einen gemeinsamen
Nachrichten-Cursor. Ein Signal, das nach einer ausstehenden Stromnachricht
zugestellt wird, verbirgt daher diese Nachricht
- Signale, Updates und alle Ströme teilen sich einen Nachrichten-Cursor.
Bis einschließlich 2.20.2 können ein Signal, ein Update oder ein Empfang
in einem anderen Strom ihn über ausstehende Nachrichten hinaus verschieben
und sie verbergen
([workflow#1011](https://github.com/durable-workflow/workflow/issues/1011)).
Empfangen Sie erst, nachdem ein Signal oder eine Bedingung anzeigt, dass eine
Nachricht vorhanden ist, und senden Sie dieses Signal vor der Nachricht.
Senden Sie für diesen Fall der Signalreihenfolge in diesen Versionen das
Signal vor der Nachricht und empfangen Sie erst, nachdem ein Signal oder
eine Bedingung anzeigt, dass eine Nachricht vorhanden ist.

:::

## Upgrades und Rollbacks von Nachrichtenströmen {#stream-upgrades}

In Workflow 2.20.3 verbirgt der gemeinsame Cursor keine ausstehenden Nachrichten
mehr vor `receive()`, `receiveOne()`, `peek()`, `pendingCount()` oder
`hasPending()`. Empfangsvorgänge behalten Sequenzreihenfolge und Stapelgrenze
bei. Nach dem Upgrade kann eine Empfangsschleife zuerst ältere ausstehende
Nachrichten zurückgeben, die der gemeinsame Cursor bisher verborgen hat, und
danach neuere Nachrichten.

Ein Empfang beim Replay gibt zurück, was sein ursprünglicher Schritt
verbraucht hat. Hat der ursprüngliche Aufruf nichts gefunden, gibt auch das
Replay nichts zurück. Ein erstmals ausgeführter Update-Handler kann weiterhin
empfangen. Ein in einer Eigenschaft aufbewahrter Strom zeichnet Empfang nun
beim aktuellen Workflow-Schritt auf, statt beim Schritt, in dem er geöffnet
wurde. Empfangsvorgänge, die frühere Releases beim Öffnungsschritt aufgezeichnet
haben, lassen sich weiterhin per Replay ausführen.

Bevor Sie eine eingebettete Anwendung auf eine Version vor 2.20.3 zurücksetzen,
wenden Sie die [Replay-Prüfung](../testing.md#replay-before-deployment) auf
Verläufe an, die das neuere Paket geschrieben hat. Frühere Versionen suchen
den Empfang aufbewahrter Ströme beim Öffnungsschritt und können solche Läufe
anders wiedergeben. Ein Rollback stellt außerdem die Fehler beim gemeinsamen
Cursor, beim Replay leerer Empfangsaufrufe und bei der Genauigkeit der
Fake-Aufgabenverarbeitung wieder her. Siehe die
[Kompatibilitätshinweise zum Tag](https://github.com/durable-workflow/workflow/blob/2.20.3/CHANGELOG.md#2203---2026-11-04).

Die Abfrageeinschränkung in
[workflow#1024](https://github.com/durable-workflow/workflow/issues/1024)
bleibt bestehen: Eine Abfrage kann für eine von einem Update-Handler empfangene
Nachricht nichts zurückgeben, weil sie den Handler beim falschen Schritt
wiedergibt. Die Abfrage ändert keine Nachrichtenzeilen. Beim eigenen Replay
des Workflows und in seiner abgeschlossenen Ausgabe wird weiterhin
zurückgegeben, was der Handler empfangen hat.

## Nachrichten empfangen {#receiving-messages}

Verwenden Sie `peek()`, wenn ein Workflow ausstehende eingehende Nachrichten
Expand Down
9 changes: 9 additions & 0 deletions i18n/de/docusaurus-plugin-content-docs/current/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ public function testTimerWorkflow(): void

`WorkflowStub::runReadyTasks()` arbeitet nur bereits fällige Aufgaben ab. Es stellt die reale Uhrzeit nicht vor und löst künftig verzögerte Timer nicht vorzeitig aus.

Workflow 2.20.3 verarbeitet Fake-Aufgaben exakt zur Fälligkeit, einschließlich
Sekundenbruchteilen, mit der gespeicherten Zeitstempelgenauigkeit des
konfigurierten Aufgabenmodells. Frühere Versionen konnten SQLite-Aufgaben
überspringen, die genau zu diesem Zeitpunkt fällig wurden. Eine künftige
Fälligkeit mit Sekundenbruchteilen bleibt bis zu ihrem Eintreten ausstehend.
Timerverzögerungen und Wiederholungs-Backoff bleiben in ganzen Sekunden.
Beachten Sie die [Grenzen für Upgrades und Rollbacks von Nachrichtenströmen](./features/message-streams.md#stream-upgrades),
bevor Sie eine eingebettete Anwendung auf ein älteres Paket zurücksetzen.

### Signale im Fake-Modus senden {#sending-signals-in-fake-mode}

Workflows, die auf Signale warten, können diese im Fake-Modus empfangen. Beim Aufruf von `$workflow->signal(...)` zeichnet die Engine den dauerhaften Signalbefehl auf und erstellt eine Workflow-Aufgabe. Im Fake-Modus wird diese direkt ausgeführt, sodass der Workflow synchron fortsetzt:
Expand Down
4 changes: 2 additions & 2 deletions i18n/de/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"features/heartbeats.md": "e3d84178788dff92954e08d08f006def0a991a481f99301facbda8c6ca21a7a0",
"features/local-activities.md": "05fca8f2104d8f4e7bd8b48040f5615f18d0ee2795922fb65c56f8d3d70ed204",
"features/memo.md": "621aa630e37fbef6f84d761c92f99dc3fe8756879008c659cbad0600eb14fd5d",
"features/message-streams.md": "b30c7629d3fb9d20f27c4bcf0551457d65ac44a541b119312a2cde31aba48354",
"features/message-streams.md": "8a21b2e6dff51c69a3395d50ea6167e418ed793b4b7107b964f2a0f5f03a3801",
"features/queries.md": "c66dd9542a062edd8158a46cebc9f1bf926aaf3599dca4a3122bd9860df6e2a2",
"features/sagas.md": "8dae955491a54bcbf545ce6972928df59f3885e00f98bf3a57b14dad98f89161",
"features/schedules.md": "717b9115ca62a69a3a1ea65f1bec68a0aebe0d5e6d2421df297bb129c597a523",
Expand Down Expand Up @@ -104,6 +104,6 @@
"sdk-neutrality.md": "7b0714c123ade4904591870fc20e9a797da1e5485b2a6c87ca3eaa00c5de6ed6",
"sponsors.md": "e85e14375743ca8fa50e3a48f894b160e28272573aa88480064f74010ebee98a",
"support.md": "706807b3902599dda6080f632ec1f893017ea18bbe9585f533dcdbd6d85f4621",
"testing.md": "b80db83e74647e9559c1f0db442dcfe7c0e636d8c9842583c69f9ae46d68633d",
"testing.md": "250bc7dfd46f99eaeb392b329947813b064bda72026645198e3aefc80043a813",
"waterline-operator-api.md": "2aca03b2c14100060b40be0bb033a82525ba924908834e9060ec746ead26f36d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,65 @@ Usa la fachada de primera clase al escribir workflows:

No escribas directamente filas `workflow_messages` ni de cursor desde el workflow de la aplicación. La fachada es el contrato estable; los servicios de nivel inferior existen para integrar paquete y runtime.

:::warning Flujos de mensajes en Workflow 2.20.0 y versiones anteriores
:::warning Flujos de mensajes anteriores a Workflow 2.20.3

El código del workflow se reproduce en cada tarea de workflow posterior y en cada consulta. En
las versiones de Workflow hasta la 2.20.0 inclusive, `outbox()->sendReference()` escribe un nuevo
mensaje saliente y entrante en cada reproducción, e `inbox()->receive()` y
`receiveOne()` pueden devolver un mensaje distinto, o ninguno, en la reproducción
([workflow#1010](https://github.com/durable-workflow/workflow/issues/1010)).
Workflow 2.20.1 lo corrige: una reproducción devuelve lo que la llamada original envió o
recibió, y una consulta nunca escribe ni consume un mensaje.
Workflow 2.20.1 registra los envíos y las recepciones para la reproducción e
impide que las consultas escriban o consuman mensajes. Las recepciones vacías
y los flujos conservados entre pasos necesitan las correcciones adicionales
de reproducción de 2.20.3 que se describen a continuación.

- Las filas que las versiones anteriores duplicaron no se eliminan. Da a cada envío
lógico un `idempotencyKey` estable, como `reply:{turn}`, y deduplica por
`idempotency_key` al consumir un flujo que escribió una versión anterior.
Los mensajes que una versión anterior consumió en el paso equivocado siguen
consumidos.
- En todas las versiones, las señales y todos los flujos comparten un único cursor de mensajes, por lo que
una señal entregada después de un mensaje pendiente de un flujo oculta ese mensaje
- Las señales, las actualizaciones y todos los flujos comparten un cursor de
mensajes. Hasta 2.20.2 inclusive, una señal, una actualización o una recepción
en otro flujo puede moverlo más allá de los mensajes pendientes y ocultarlos
([workflow#1011](https://github.com/durable-workflow/workflow/issues/1011)).
Recibe solo después de que una señal o una condición indique que hay un mensaje, y envía
esa señal antes del mensaje.
Para ese caso de orden de señales en esas versiones, envía la señal antes
del mensaje y recibe solo después de que una señal o condición indique que
hay un mensaje.

:::

## Actualizaciones de flujos y reversión {#stream-upgrades}

En Workflow 2.20.3, el cursor compartido ya no oculta mensajes pendientes a
`receive()`, `receiveOne()`, `peek()`, `pendingCount()` ni `hasPending()`. Las
recepciones mantienen el orden de secuencia y su límite de lote. Tras
actualizar, un bucle de recepción puede devolver primero mensajes pendientes
antiguos que el cursor compartido ocultaba y después los más recientes.

Una recepción reproducida devuelve lo que consumió su paso original. Si la
llamada original no encontró nada, la reproducción tampoco devuelve nada.
Un manejador de actualización que se ejecuta por primera vez sigue pudiendo
recibir. Un flujo conservado en una propiedad ahora registra las recepciones
en el paso actual del workflow, en lugar del paso en que se abrió el flujo.
Las recepciones que versiones anteriores registraron en el paso de apertura
siguen reproduciéndose.

Antes de revertir una aplicación integrada a una versión anterior a 2.20.3,
aplica el [control de reproducción](../testing.md#replay-before-deployment)
con historiales escritos por el paquete más reciente. Las versiones
anteriores buscan las recepciones de un flujo conservado en el paso de
apertura y pueden reproducir esas ejecuciones de otra manera. La reversión
también restaura los errores del cursor compartido, la reproducción de
recepciones vacías y la precisión del procesamiento de tareas fake. Consulta las
[notas de compatibilidad de la etiqueta](https://github.com/durable-workflow/workflow/blob/2.20.3/CHANGELOG.md#2203---2026-11-04).

La limitación de consultas en
[workflow#1024](https://github.com/durable-workflow/workflow/issues/1024)
permanece: una consulta puede no devolver un mensaje que recibió un manejador
de actualización porque reproduce el manejador en el paso equivocado. La
consulta no cambia filas de mensajes. La reproducción del propio workflow y
su salida completada siguen devolviendo lo que recibió el manejador.

## Recibir mensajes {#receiving-messages}

Usa `peek()` para inspeccionar mensajes pendientes sin avanzar el cursor duradero. Usa `receive()` o `receiveOne()` cuando estés listo para consumirlos y registrar el avance en el historial.
Expand Down
10 changes: 10 additions & 0 deletions i18n/es/docusaurus-plugin-content-docs/current/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ public function testTimerWorkflow(): void

`WorkflowStub::runReadyTasks()` solo ejecuta tareas cuyo plazo ya llegó. No adelanta el reloj ni fuerza la activación anticipada de temporizadores futuros.

Workflow 2.20.3 procesa las tareas fake en su instante exacto de vencimiento,
incluidas las fracciones de segundo, con la precisión de marcas temporales
que persiste el modelo de tareas configurado. Las versiones anteriores
podían omitir tareas de SQLite que vencían en ese instante. Un plazo futuro
con fracciones de segundo permanece pendiente hasta su vencimiento. Los
retrasos de temporizador y el backoff de reintento siguen usando segundos
enteros. Consulta los
[límites de actualización y reversión de flujos](./features/message-streams.md#stream-upgrades)
antes de revertir una aplicación integrada a un paquete anterior.

### Enviar señales en modo simulado {#sending-signals-in-fake-mode}

Los workflows que esperan señales pueden recibirlas en modo simulado. Al llamar a `$workflow->signal(...)`, el motor registra el comando duradero y crea una tarea de workflow. Esa tarea se ejecuta inmediatamente en modo simulado y el workflow se reanuda de forma síncrona:
Expand Down
4 changes: 2 additions & 2 deletions i18n/es/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"features/heartbeats.md": "e3d84178788dff92954e08d08f006def0a991a481f99301facbda8c6ca21a7a0",
"features/local-activities.md": "05fca8f2104d8f4e7bd8b48040f5615f18d0ee2795922fb65c56f8d3d70ed204",
"features/memo.md": "621aa630e37fbef6f84d761c92f99dc3fe8756879008c659cbad0600eb14fd5d",
"features/message-streams.md": "b30c7629d3fb9d20f27c4bcf0551457d65ac44a541b119312a2cde31aba48354",
"features/message-streams.md": "8a21b2e6dff51c69a3395d50ea6167e418ed793b4b7107b964f2a0f5f03a3801",
"features/queries.md": "c66dd9542a062edd8158a46cebc9f1bf926aaf3599dca4a3122bd9860df6e2a2",
"features/sagas.md": "8dae955491a54bcbf545ce6972928df59f3885e00f98bf3a57b14dad98f89161",
"features/schedules.md": "717b9115ca62a69a3a1ea65f1bec68a0aebe0d5e6d2421df297bb129c597a523",
Expand Down Expand Up @@ -104,6 +104,6 @@
"sdk-neutrality.md": "7b0714c123ade4904591870fc20e9a797da1e5485b2a6c87ca3eaa00c5de6ed6",
"sponsors.md": "e85e14375743ca8fa50e3a48f894b160e28272573aa88480064f74010ebee98a",
"support.md": "706807b3902599dda6080f632ec1f893017ea18bbe9585f533dcdbd6d85f4621",
"testing.md": "b80db83e74647e9559c1f0db442dcfe7c0e636d8c9842583c69f9ae46d68633d",
"testing.md": "250bc7dfd46f99eaeb392b329947813b064bda72026645198e3aefc80043a813",
"waterline-operator-api.md": "2aca03b2c14100060b40be0bb033a82525ba924908834e9060ec746ead26f36d"
}
Loading
Loading