From 13831eb2215faf47694c822bda014d6d275a21d9 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Fri, 23 Oct 2026 09:07:30 +0000 Subject: [PATCH 1/2] docs: explain Python heartbeat refusals and callback exit --- docs/features/timeouts.md | 2 ++ docs/polyglot/python.md | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/features/timeouts.md b/docs/features/timeouts.md index 056aa5fc1a3..ad20dd022c2 100644 --- a/docs/features/timeouts.md +++ b/docs/features/timeouts.md @@ -204,6 +204,8 @@ $result = activity( ); ``` +In service mode, closing an activity attempt does not forcibly stop SDK application code. Python SDK 2.9.0 reports a refusal at the next heartbeat; code that never heartbeats can run until it returns, and a physical thread slot remains occupied until exit. See [Python heartbeat refusals and callback exit](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Schedule-to-start timeout The deadline is computed when the activity is scheduled. If no worker claims the activity before the deadline, the `TaskWatchdog` enforces the timeout. If retry attempts remain, a new activity task is scheduled with the snapped backoff and the schedule-to-start deadline is recomputed relative to the retry's available-at time — each retry gets a fresh window. If no `scheduleToStartTimeout` was configured, the deadline is cleared on retry. If all attempts are exhausted, a terminal `ActivityTimedOut` history event is recorded and the workflow is woken. diff --git a/docs/polyglot/python.md b/docs/polyglot/python.md index 3ead428dce2..a9c9b61ac22 100644 --- a/docs/polyglot/python.md +++ b/docs/polyglot/python.md @@ -1066,6 +1066,14 @@ The direct `Client.heartbeat_activity_task()` call uses the configured `Transpor Do not suppress heartbeat/ownership failures and continue external work or report success. Progress is not an external commit or a resumable-checkpoint receipt. Keep external effects idempotent. +### Heartbeat refusals and callback exit {#heartbeat-refusals-and-callback-exit} + +In **Python SDK 2.9.0**, `ctx.heartbeat()` and `ctx.heartbeat_sync()` raise `ActivityLeaseAuthorityLost` when a heartbeat response says `can_continue=false`. The exception retains the original Server reason, such as `activity_deadline_elapsed`, `attempt_closed` or `run_closed`. A cancellation request instead raises `ActivityCancelled`. See the [published SDK qualification](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +The heartbeat that observes the refusal is a cooperative exit point. Stop external work and propagate the exception; do not catch it and return success. Code that never heartbeats can keep running until it returns. A helper heartbeat cannot forcibly interrupt a blocked library call: use finite I/O deadlines and the call's own cancellation mechanism. + +The physical activity slot stays occupied until the callback/thread exits. Losing attempt authority does not make that thread available for replacement work, and the worker does not publish a late result for the closed attempt. Runtime timeout enforcement and stopping application code are separate boundaries. See [activity timeouts](../features/timeouts.md#activity-timeouts) and the [helper-thread guidance](#heartbeat-helper-thread). + ### Non-Retryable Errors Raise `NonRetryableError` to fail the activity without retries: From 6949bfa33e8dd4dda166f180d6dd4422bf24b4b9 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Fri, 23 Oct 2026 09:09:50 +0000 Subject: [PATCH 2/2] docs: translate Python refusal boundaries into all seven locales --- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/de/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/es/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/fr/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/ja/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/pt-BR/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/uk/source-hashes.json | 4 ++-- .../current/features/timeouts.md | 2 ++ .../current/polyglot/python.md | 8 ++++++++ i18n/zh-Hans/source-hashes.json | 4 ++-- 21 files changed, 84 insertions(+), 14 deletions(-) diff --git a/i18n/de/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/de/docusaurus-plugin-content-docs/current/features/timeouts.md index e23eac41688..14c8e267518 100644 --- a/i18n/de/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/de/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -188,6 +188,8 @@ $result = activity( ); ``` +Im Service-Modus hält das Schließen eines Activity-Versuchs den SDK-Anwendungscode nicht zwangsweise an. Python SDK 2.9.0 meldet die Ablehnung beim nächsten Heartbeat; Code ohne Heartbeats kann bis zur Rückkehr weiterlaufen, und der physische Thread-Slot bleibt bis zum Ende belegt. Siehe [Heartbeat-Ablehnungen und Callback-Ende in Python](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Zeitlimit von Planung bis Start {#schedule-to-start-timeout} Die Frist wird bei der Planung der Aktivität berechnet. Beansprucht kein Worker sie rechtzeitig, setzt `TaskWatchdog` das Zeitlimit durch. Sind weitere Versuche möglich, wird eine neue Aufgabe mit der gespeicherten Wartezeit geplant und die Schedule-to-start-Frist relativ zur Verfügbarkeit dieses Versuchs neu berechnet. Jeder Versuch erhält ein neues Fenster. Ohne konfiguriertes `scheduleToStartTimeout` wird die Frist beim Wiederholungsversuch gelöscht. Nach Ausschöpfen aller Versuche wird ein terminales `ActivityTimedOut`-Ereignis gespeichert und der Workflow geweckt. diff --git a/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md index 66f76127e48..874300689f6 100644 --- a/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1039,6 +1039,14 @@ Der direkte Aufruf von `Client.heartbeat_activity_task()` verwendet die konfigur Unterdrücken Sie Heartbeat- oder Besitzfehler nicht, um externe Arbeit fortzusetzen oder Erfolg zu melden. Fortschritt ist weder ein externer Commit noch eine Bestätigung eines fortsetzbaren Prüfpunkts. Halten Sie externe Effekte idempotent. +### Heartbeat-Ablehnung und Ende des Callbacks {#heartbeat-refusals-and-callback-exit} + +Im **Python SDK 2.9.0** lösen `ctx.heartbeat()` und `ctx.heartbeat_sync()` einen `ActivityLeaseAuthorityLost` aus, wenn eine Heartbeat-Antwort `can_continue=false` enthält. Die Ausnahme bewahrt den ursprünglichen Server-Grund, etwa `activity_deadline_elapsed`, `attempt_closed` oder `run_closed`. Eine Abbruchanforderung löst dagegen `ActivityCancelled` aus. Siehe die [Qualifikation des veröffentlichten SDK](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +Der Heartbeat, der die Ablehnung empfängt, ist ein kooperativer Ausstiegspunkt. Beenden Sie externe Arbeit und reichen Sie die Ausnahme weiter; fangen Sie sie nicht ab, um Erfolg zurückzugeben. Code ohne Heartbeats kann bis zur Rückkehr weiterlaufen. Ein Hilfs-Heartbeat kann einen blockierten Bibliotheksaufruf nicht zwangsweise unterbrechen: Verwenden Sie endliche E/A-Fristen und den eigenen Abbruchmechanismus des Aufrufs. + +Der physische Activity-Slot bleibt bis zum Ende des Callbacks/Threads belegt. Der Verlust der Ausführungsberechtigung gibt diesen Thread nicht für andere Arbeit frei, und der Worker veröffentlicht kein verspätetes Ergebnis für den geschlossenen Versuch. Die Durchsetzung eines Runtime-Timeouts und das Anhalten von Anwendungscode sind getrennte Grenzen. Siehe [Activity-Timeouts](../features/timeouts.md#activity-timeouts) und die [Hinweise zum Hilfsthread](#heartbeat-helper-thread). + ### Nicht wiederholbare Fehler {#non-retryable-errors} Lösen Sie `NonRetryableError` aus, um eine Aktivität ohne Wiederholungen scheitern zu lassen: diff --git a/i18n/de/source-hashes.json b/i18n/de/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/de/source-hashes.json +++ b/i18n/de/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/es/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/es/docusaurus-plugin-content-docs/current/features/timeouts.md index d118ea5b060..9b7a13ab125 100644 --- a/i18n/es/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/es/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -188,6 +188,8 @@ $result = activity( ); ``` +En modo servicio, cerrar un intento de actividad no detiene por la fuerza el código de la aplicación del SDK. Python SDK 2.9.0 comunica el rechazo en el siguiente heartbeat; el código sin heartbeats puede ejecutarse hasta retornar y la plaza física del hilo permanece ocupada hasta su salida. Consulte [rechazos de heartbeat y salida del callback en Python](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Tiempo de espera de programación a inicio {#schedule-to-start-timeout} El plazo se calcula al programar la actividad. Si ningún worker la reclama antes, `TaskWatchdog` aplica el tiempo de espera. Si quedan intentos, se programa una nueva tarea de actividad con la espera registrada y el plazo schedule-to-start se recalcula desde el momento en que el nuevo intento esté disponible. Cada intento recibe una ventana nueva. Si no se configuró `scheduleToStartTimeout`, el plazo se borra al reintentar. Si se agotan los intentos, se registra el evento terminal `ActivityTimedOut` y se despierta el workflow. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md index 7c1ad0f28a7..a24979805a2 100644 --- a/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1016,6 +1016,14 @@ La llamada directa a `Client.heartbeat_activity_task()` usa la `TransportRetryPo No suprima fallos de heartbeat o de titularidad para seguir haciendo trabajo externo o informar de éxito. El progreso no es una confirmación externa ni un recibo de punto de control reanudable. Mantenga idempotentes los efectos externos. +### Rechazo de heartbeats y salida del callback {#heartbeat-refusals-and-callback-exit} + +En **Python SDK 2.9.0**, `ctx.heartbeat()` y `ctx.heartbeat_sync()` lanzan `ActivityLeaseAuthorityLost` cuando una respuesta de heartbeat indica `can_continue=false`. La excepción conserva el motivo original del Server, como `activity_deadline_elapsed`, `attempt_closed` o `run_closed`. Una solicitud de cancelación lanza `ActivityCancelled`. Consulte la [validación del SDK publicado](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +El heartbeat que observa el rechazo es un punto de salida cooperativa. Detenga el trabajo externo y propague la excepción; no la capture para devolver un resultado correcto. El código que nunca envía heartbeats puede seguir ejecutándose hasta retornar. Un heartbeat auxiliar no puede interrumpir por la fuerza una llamada bloqueada de una biblioteca: use plazos finitos de E/S y el mecanismo de cancelación de la propia llamada. + +La plaza física de actividad permanece ocupada hasta que sale el callback/hilo. Perder la autoridad del intento no libera ese hilo para otro trabajo, y el worker no publica un resultado tardío del intento cerrado. Aplicar el timeout del runtime y detener el código de la aplicación son límites distintos. Consulte los [timeouts de actividad](../features/timeouts.md#activity-timeouts) y la [guía del hilo auxiliar](#heartbeat-helper-thread). + ### Errores no reintentables {#non-retryable-errors} Lanza `NonRetryableError` para fallar sin reintentos: diff --git a/i18n/es/source-hashes.json b/i18n/es/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/es/source-hashes.json +++ b/i18n/es/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/fr/docusaurus-plugin-content-docs/current/features/timeouts.md index 075ba4741fb..b407f63cba0 100644 --- a/i18n/fr/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/fr/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -188,6 +188,8 @@ $result = activity( ); ``` +En mode service, fermer une tentative d’activité n’arrête pas de force le code applicatif du SDK. Le SDK Python 2.9.0 signale le refus au prochain heartbeat ; le code sans heartbeat peut s’exécuter jusqu’à son retour et le créneau physique du thread reste occupé jusqu’à sa sortie. Consultez les [refus de heartbeat et la sortie du callback en Python](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Délai entre planification et démarrage {#schedule-to-start-timeout} L’échéance est calculée à la planification de l’activité. Si aucun worker ne l’a réclamée à temps, `TaskWatchdog` applique le délai. S’il reste des tentatives, une nouvelle tâche est planifiée avec le recul enregistré et l’échéance schedule-to-start est recalculée depuis la disponibilité du nouvel essai. Chaque tentative reçoit une nouvelle fenêtre. Sans `scheduleToStartTimeout` configuré, l’échéance est effacée lors du nouvel essai. Une fois toutes les tentatives épuisées, un événement terminal `ActivityTimedOut` est enregistré et le workflow est réveillé. diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md index 780395e8eac..17e03bb44e5 100644 --- a/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1039,6 +1039,14 @@ L’appel direct à `Client.heartbeat_activity_task()` utilise la `TransportRetr Ne masquez pas les échecs de heartbeat ou de possession pour poursuivre le travail externe ou annoncer une réussite. La progression n’est ni une validation externe ni une confirmation de point de reprise. Gardez les effets externes idempotents. +### Refus du heartbeat et sortie du callback {#heartbeat-refusals-and-callback-exit} + +Avec le **SDK Python 2.9.0**, `ctx.heartbeat()` et `ctx.heartbeat_sync()` lèvent `ActivityLeaseAuthorityLost` lorsqu’une réponse de heartbeat indique `can_continue=false`. L’exception conserve le motif initial du Server, par exemple `activity_deadline_elapsed`, `attempt_closed` ou `run_closed`. Une demande d’annulation lève plutôt `ActivityCancelled`. Consultez la [qualification du SDK publié](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +Le heartbeat qui constate le refus constitue un point de sortie coopérative. Arrêtez le travail externe et propagez l’exception ; ne l’interceptez pas pour renvoyer un succès. Le code qui n’envoie jamais de heartbeat peut continuer jusqu’à son retour. Un heartbeat auxiliaire ne peut pas interrompre de force un appel de bibliothèque bloqué : utilisez des échéances d’E/S finies et le mécanisme d’annulation propre à l’appel. + +Le créneau physique de l’activité reste occupé jusqu’à la sortie du callback/thread. La perte d’autorité de la tentative ne libère pas ce thread pour un autre travail, et le worker ne publie pas de résultat tardif pour la tentative fermée. L’application du timeout par le runtime et l’arrêt du code applicatif sont des limites distinctes. Consultez les [timeouts d’activité](../features/timeouts.md#activity-timeouts) et les [conseils pour le thread auxiliaire](#heartbeat-helper-thread). + ### Erreurs non réessayables {#non-retryable-errors} Levez `NonRetryableError` pour faire échouer l'activité sans nouvelle tentative : diff --git a/i18n/fr/source-hashes.json b/i18n/fr/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/fr/source-hashes.json +++ b/i18n/fr/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/ja/docusaurus-plugin-content-docs/current/features/timeouts.md index 21f7ed7d14a..2c4cffd7dbe 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -186,6 +186,8 @@ $result = activity( ); ``` +サービスモードでは、アクティビティの試行を閉じても SDK のアプリケーションコードは強制停止されません。Python SDK 2.9.0 は次のハートビートで拒否を通知します。ハートビートを送らないコードは戻るまで実行を続ける可能性があり、物理スレッドのスロットは終了まで占有されます。[Python のハートビート拒否とコールバック終了](../polyglot/python.md#heartbeat-refusals-and-callback-exit)を参照してください。 + ### スケジュールから開始までのタイムアウト {#schedule-to-start-timeout} 期限はアクティビティをスケジュールした時点で計算します。期限までに Worker が取得しなければ、`TaskWatchdog` がタイムアウトを適用します。試行回数が残っていれば、固定済みのバックオフで新しいタスクをスケジュールし、その実行可能時刻から開始待ち期限を再計算します。各再試行には新しい時間枠が与えられます。`scheduleToStartTimeout` が未設定なら、再試行時にこの期限を消去します。すべての試行が尽きると、終端の `ActivityTimedOut` 履歴を記録してワークフローを起こします。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md index 2979dae23d6..3ee4320f025 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -860,6 +860,14 @@ async def long_running_task(items: list) -> dict: ハートビートや所有権の失敗を抑制して外部処理を続けたり、成功を報告したりしないでください。進捗は外部コミットや再開可能なチェックポイントの確認ではありません。外部作用は冪等にしてください。 +### ハートビートの拒否とコールバックの終了 {#heartbeat-refusals-and-callback-exit} + +**Python SDK 2.9.0** では、ハートビートの応答が `can_continue=false` を示すと、`ctx.heartbeat()` と `ctx.heartbeat_sync()` は `ActivityLeaseAuthorityLost` を送出します。例外には `activity_deadline_elapsed`、`attempt_closed`、`run_closed` など、Server の元の理由が保持されます。キャンセル要求の場合は `ActivityCancelled` を送出します。[公開済み SDK の検証結果](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674)を参照してください。 + +拒否を受け取ったハートビートが、協調的に終了する地点です。外部処理を停止し、例外を再送出してください。例外を捕捉して成功を返してはいけません。ハートビートを一度も送らないコードは、戻るまで実行を続ける可能性があります。補助ハートビートでは、ブロック中のライブラリ呼び出しを強制的に中断できません。有限の I/O 期限と、その呼び出し自体のキャンセル機構を使用してください。 + +物理アクティビティスロットは、コールバックまたはスレッドが終了するまで占有されます。試行の実行権限を失っても、そのスレッドが別の処理に使えるようにはならず、Worker は終了済みの試行に遅れた結果を送信しません。ランタイムによるタイムアウトの適用と、アプリケーションコードの停止は別の境界です。[アクティビティのタイムアウト](../features/timeouts.md#activity-timeouts)と[補助スレッドのガイド](#heartbeat-helper-thread)を参照してください。 + ### 再試行しないエラー {#non-retryable-errors} 再試行せずにアクティビティを失敗させるには `NonRetryableError` を送出します。 diff --git a/i18n/ja/source-hashes.json b/i18n/ja/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/ja/source-hashes.json +++ b/i18n/ja/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/timeouts.md index f5098d2b2b5..f4211d3b430 100644 --- a/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/pt-BR/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -188,6 +188,8 @@ $result = activity( ); ``` +No modo serviço, encerrar uma tentativa de atividade não para à força o código da aplicação do SDK. O Python SDK 2.9.0 comunica a recusa no próximo heartbeat; código sem heartbeats pode executar até retornar, e o slot físico da thread permanece ocupado até sua saída. Consulte [recusas de heartbeat e saída do callback em Python](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Tempo limite entre agendamento e início {#schedule-to-start-timeout} O prazo é calculado ao agendar a atividade. Se nenhum worker a reivindicar antes, `TaskWatchdog` aplica o tempo limite. Se restarem tentativas, uma nova tarefa de atividade é agendada com a espera registrada e o prazo schedule-to-start é recalculado a partir do instante em que a nova tentativa fica disponível. Cada tentativa recebe uma nova janela. Se `scheduleToStartTimeout` não foi configurado, o prazo é removido ao tentar novamente. Se as tentativas se esgotarem, o evento terminal `ActivityTimedOut` é registrado e o workflow é despertado. diff --git a/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md index 831d3c23bbc..eaef30e8aef 100644 --- a/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1039,6 +1039,14 @@ A chamada direta a `Client.heartbeat_activity_task()` usa a `TransportRetryPolic Não suprima falhas de heartbeat ou de posse para continuar trabalho externo ou informar sucesso. Progresso não é um commit externo nem uma confirmação de checkpoint retomável. Mantenha os efeitos externos idempotentes. +### Recusas de heartbeat e saída do callback {#heartbeat-refusals-and-callback-exit} + +No **Python SDK 2.9.0**, `ctx.heartbeat()` e `ctx.heartbeat_sync()` lançam `ActivityLeaseAuthorityLost` quando uma resposta de heartbeat informa `can_continue=false`. A exceção preserva o motivo original do Server, como `activity_deadline_elapsed`, `attempt_closed` ou `run_closed`. Uma solicitação de cancelamento lança `ActivityCancelled`. Consulte a [qualificação do SDK publicado](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +O heartbeat que observa a recusa é um ponto de saída cooperativa. Interrompa o trabalho externo e propague a exceção; não a capture para retornar sucesso. Código que nunca envia heartbeats pode continuar executando até retornar. Um heartbeat auxiliar não pode interromper à força uma chamada de biblioteca bloqueada: use prazos finitos de E/S e o mecanismo de cancelamento da própria chamada. + +O slot físico da atividade permanece ocupado até a saída do callback/thread. Perder a autoridade da tentativa não libera essa thread para outro trabalho, e o worker não publica um resultado tardio da tentativa encerrada. Aplicar o timeout do runtime e parar o código da aplicação são limites distintos. Consulte os [timeouts de atividade](../features/timeouts.md#activity-timeouts) e a [orientação sobre a thread auxiliar](#heartbeat-helper-thread). + ### Erros que não permitem nova tentativa {#non-retryable-errors} Lance `NonRetryableError` para falhar a atividade sem novas tentativas: diff --git a/i18n/pt-BR/source-hashes.json b/i18n/pt-BR/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/pt-BR/source-hashes.json +++ b/i18n/pt-BR/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md index 7572b0e5a3a..343832cbe38 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -205,6 +205,8 @@ $result = activity( ); ``` +У сервісному режимі закриття спроби activity не зупиняє примусово прикладний код SDK. Python SDK 2.9.0 повідомляє відмову під час наступного heartbeat; код без heartbeat може працювати до повернення, а фізичний слот потоку зайнятий до завершення. Див. [відмови heartbeat і завершення callback у Python](../polyglot/python.md#heartbeat-refusals-and-callback-exit). + ### Тайм-аут schedule-to-start {#schedule-to-start-timeout} Кінцевий термін обчислюється під час планування activity. Якщо до цього терміну жоден worker не отримає activity, `TaskWatchdog` застосовує тайм-аут. Якщо повторні спроби ще доступні, нове завдання activity планується із зафіксованою затримкою, а кінцевий термін schedule-to-start перераховується від моменту доступності повторної спроби. Кожна спроба отримує нове часове вікно. Якщо `scheduleToStartTimeout` не налаштовано, під час повторної спроби кінцевий термін очищається. Коли всі спроби вичерпано, записується термінальна подія історії `ActivityTimedOut` і workflow пробуджується. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md index 66b9a4d651f..5eaa468ffc6 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1024,6 +1024,14 @@ async def long_running_task(items: list) -> dict: Не приховуйте збоїв heartbeat чи повноважень, щоб продовжити зовнішню роботу або повідомити про успіх. Прогрес не є зовнішнім commit або підтвердженням відновлюваної контрольної точки. Зберігайте зовнішні ефекти ідемпотентними. +### Відмова heartbeat і завершення callback {#heartbeat-refusals-and-callback-exit} + +У **Python SDK 2.9.0** `ctx.heartbeat()` і `ctx.heartbeat_sync()` піднімають `ActivityLeaseAuthorityLost`, коли відповідь heartbeat містить `can_continue=false`. Виняток зберігає початкову причину Server, наприклад `activity_deadline_elapsed`, `attempt_closed` або `run_closed`. Натомість запит скасування піднімає `ActivityCancelled`. Див. [кваліфікацію опублікованого SDK](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674). + +Heartbeat, який отримав відмову, є точкою кооперативного завершення. Припиніть зовнішню роботу й передайте виняток далі; не перехоплюйте його, щоб повернути успіх. Код без heartbeat може працювати до свого повернення. Допоміжний heartbeat не може примусово перервати заблокований виклик бібліотеки: задавайте скінченні дедлайни I/O та використовуйте механізм скасування самого виклику. + +Фізичний слот activity зайнятий до завершення callback/потоку. Втрата повноважень спроби не звільняє цей потік для іншої роботи, а worker не публікує запізнілий результат закритої спроби. Застосування таймауту runtime й зупинка прикладного коду — окремі межі. Див. [таймаути activity](../features/timeouts.md#activity-timeouts) і [поради щодо допоміжного потоку](#heartbeat-helper-thread). + ### Неповторювані помилки {#non-retryable-errors} Підніміть `NonRetryableError`, щоб завершити activity помилкою без retry: diff --git a/i18n/uk/source-hashes.json b/i18n/uk/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/uk/source-hashes.json +++ b/i18n/uk/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83", diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/timeouts.md index 40393c9c152..b93ec35fae1 100644 --- a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/timeouts.md +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -186,6 +186,8 @@ $result = activity( ); ``` +在服务模式下,关闭活动尝试不会强制停止 SDK 应用代码。Python SDK 2.9.0 会在下一次心跳时报告拒绝;不发送心跳的代码可能一直运行到返回,而物理线程槽位会一直占用到退出。参见 [Python 心跳拒绝与回调退出](../polyglot/python.md#heartbeat-refusals-and-callback-exit)。 + ### 调度到开始超时 {#schedule-to-start-timeout} 截止时间在活动被调度时计算。若没有 Worker 在期限前领取,`TaskWatchdog` 执行超时。如果还可重试,则使用已快照的退避调度新活动任务,并相对于重试的可用时间重新计算调度到开始截止时间,每次重试都有新窗口。若未配置 `scheduleToStartTimeout`,重试时会清除此截止时间。所有尝试用尽后,记录终态 `ActivityTimedOut` 历史事件并唤醒工作流。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md index b9947efca19..16a8ccf6cf1 100644 --- a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -846,6 +846,14 @@ async def long_running_task(items: list) -> dict: 不要忽略心跳或所有权故障后继续执行外部工作或报告成功。进度不是外部提交,也不是可恢复检查点的确认。请保持外部副作用幂等。 +### 心跳拒绝与回调退出 {#heartbeat-refusals-and-callback-exit} + +在 **Python SDK 2.9.0** 中,当心跳响应包含 `can_continue=false` 时,`ctx.heartbeat()` 和 `ctx.heartbeat_sync()` 会抛出 `ActivityLeaseAuthorityLost`。异常保留 Server 的原始原因,例如 `activity_deadline_elapsed`、`attempt_closed` 或 `run_closed`。取消请求则会抛出 `ActivityCancelled`。参见[已发布 SDK 的验证结果](https://github.com/durable-workflow/sdk-python/issues/186#issuecomment-6361330674)。 + +收到拒绝的那次心跳是协作退出点。停止外部工作并继续抛出异常;不要捕获异常后返回成功。从不发送心跳的代码可能一直运行到返回。辅助心跳无法强行中断被阻塞的库调用:应设置有限的 I/O 截止时间,并使用调用自身的取消机制。 + +物理活动槽位会一直占用到回调或线程退出。尝试失去执行权限并不会让该线程可用于其他工作,Worker 也不会为已关闭的尝试发布迟到的结果。运行时执行超时处理与停止应用代码是两个不同的边界。参见[活动超时](../features/timeouts.md#activity-timeouts)和[辅助线程指南](#heartbeat-helper-thread)。 + ### 不可重试错误 {#non-retryable-errors} 抛出 `NonRetryableError`,使活动失败且不重试: diff --git a/i18n/zh-Hans/source-hashes.json b/i18n/zh-Hans/source-hashes.json index 6d659f850f1..08c20f87ab5 100644 --- a/i18n/zh-Hans/source-hashes.json +++ b/i18n/zh-Hans/source-hashes.json @@ -50,7 +50,7 @@ "features/signal+timer.md": "65b4a78b9e90a845297b1cb12591dd26a6573f76c75717e1e4ba02211c590822", "features/signals.md": "2816f53ec12f5ae88906a399a9681e8a517677d64ce6c063dd4eb205c46f370b", "features/sticky-execution.md": "d54fe85283d633d5cba10a03c716a707bf3b6a4e6d47ec9859e17f5f676a89b8", - "features/timeouts.md": "785dea4e9b8f8fa9c0c9e880b8c20f1847745c244391800e289fa9ccd9438699", + "features/timeouts.md": "e9c1b46c679dae3acfcdc199b8f3e07defd7b114dfd2d8fe6c9ac20285b776aa", "features/timers.md": "94a017d85c9dc7e8e598ce895bb722523422e3729b1935ede777ed7dd2734db7", "features/updates.md": "ecc24a4ae764edf0281430583216d47d4b75fd9f58d183f7dd5bc9794cbd1c06", "features/versioning.md": "3686d25c27aa8a4f576b1fb01c554c6d58f8fa91363d64314830a325872a1ed3", @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1", + "polyglot/python.md": "8776584d0bb4b5667f719e698cea654f53f16cf927265fae27c2930779188648", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0", "polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83",