You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For a deeper tour of the async authoring surface (determinism helpers, sandbox modes, timeouts, concurrency patterns), see the Async Enhancements guide: [ASYNC_ENHANCEMENTS.md](./ASYNC_ENHANCEMENTS.md). The developer-facing migration notes are in [DEVELOPER_TRANSITION_GUIDE.md](./DEVELOPER_TRANSITION_GUIDE.md).
361
+
For a deeper tour of the async authoring surface (determinism helpers, sandbox modes, timeouts, concurrency patterns), see the Async Enhancements guide: [ASYNC_ENHANCEMENTS.md](./ASYNC_ENHANCEMENTS.md).
362
362
363
363
You can author orchestrators with `async def` using the new `durabletask.aio` package, which provides a comprehensive async workflow API:
364
364
@@ -376,9 +376,11 @@ with TaskHubGrpcWorker() as worker:
376
376
worker.add_orchestrator(my_orch)
377
377
```
378
378
379
-
Optional sandbox mode (`best_effort` or `strict`) patches `asyncio.sleep`, `random`, `uuid.uuid4`, and `time.time` within the workflow step to deterministic equivalents. This is best-effort and not a correctness guarantee.
379
+
The sandbox (enabled by default) patches standard Python functions to deterministic equivalents during workflow execution. This allows natural async code like `asyncio.sleep()`, `random.random()`, and `asyncio.gather()` to work correctly with workflow replay. Three modes are available:
380
380
381
-
In `strict` mode, `asyncio.create_task` is blocked inside workflows to preserve determinism and will raise a `SandboxViolationError` if used.
-`"off"`: No patching (requires manual use of `ctx.*` methods everywhere)
382
384
383
385
> **Enhanced Sandbox Features**: The enhanced version includes comprehensive non-determinism detection, timeout support, enhanced concurrency primitives, and debugging tools. See [ASYNC_ENHANCEMENTS.md](./durabletask/aio/ASYNCIO_ENHANCEMENTS.md) for complete documentation.
384
386
@@ -406,13 +408,10 @@ val = await ctx.wait_for_external_event("approval")
- Useful for routing, observability, and cross-cutting concerns passed along activity/sub-orchestrator calls via the sidecar.
492
-
- In python-sdk, available for both async and generator orchestrators. In this repo, currently implemented on `durabletask.aio`; generator parity is planned.
493
-
494
470
- Cross-app activity/sub-orchestrator routing (async only for now):
- Catch accidental non-determinism in development (BEST_EFFORT) before it ships.
140
-
- Keep production fast with zero overhead (OFF).
141
-
- Enforce determinism in CI (STRICT) to prevent regressions.
137
+
Why "best_effort" is the default:
138
+
- Makes standard asyncio patterns work correctly (asyncio.sleep, asyncio.gather, etc.)
139
+
- Patches random/time/uuid to be deterministic automatically
140
+
- Optional warnings only when debug mode is enabled (low overhead)
141
+
- Provides "pit of success" for async workflow authoring
142
142
143
143
### Performance Impact
144
-
-`"off"`: Zero overhead (recommended for production)
145
-
-`"best_effort"/"strict"`: ~100-200% overhead due to Python tracing
144
+
-`"best_effort"` (default): Minimal overhead from function patching. Tracing overhead present but uses lightweight noop tracer unless debug mode is enabled.
145
+
-`"strict"`: ~100-200% overhead due to full Python tracing for detection
146
+
-`"off"`: Zero overhead (no patching, no tracing)
146
147
- Global disable: Set `DAPR_WF_DISABLE_DETERMINISTIC_DETECTION=true` environment variable
147
148
149
+
Note: Function patching overhead is minimal (single-digit percentage). Tracing overhead (when enabled) is more significant due to Python's sys.settrace() mechanism.
The sandbox provides optional, scoped compatibility and detection for common non‑deterministic stdlib calls. It is opt‑in per orchestrator via `sandbox_mode`:
143
+
The sandbox provides scoped compatibility and detection for common non‑deterministic stdlib calls. It is configured per orchestrator via `sandbox_mode`:
144
144
145
-
-`off` (default): No patching or detection; zero overhead. Use deterministic APIs only.
146
-
-`best_effort`: Patch common functions within a scope and emit warnings on detected non‑determinism.
147
-
-`strict`: As above, but raise `SandboxViolationError` on detected calls.
145
+
-`best_effort` (default): Patch common functions within a scope and emit warnings on detected non‑determinism when debug mode is enabled.
146
+
-`strict`: Patch common functions and raise `SandboxViolationError` on detected calls.
147
+
-`off`: No patching or detection; zero overhead. Use deterministic APIs only.
148
148
149
-
Patched targets (best‑effort):
149
+
Patched targets (best‑effort and strict):
150
150
-`asyncio.sleep` → deterministic timer awaitable
151
-
-`random` module functions (via a deterministic `Random` instance)
151
+
-`asyncio.gather` → replay-safe one-shot awaitable wrapper using WhenAllAwaitable
152
+
-`random` module functions (random, randrange, randint, getrandbits via deterministic PRNG)
- Raises `SandboxViolationError` on first detection with details and suggestions
185
192
186
-
When to use it (recommended):
187
-
-During development to quickly surface accidental non‑determinism in orchestrator code
188
-
-When integrating third‑party libraries that might call time/random/uuid internally
189
-
-In CI for a dedicated “determinism” job (short test matrix), using `BEST_EFFORT` for warnings or `STRICT` for enforcement
193
+
When to use each mode:
194
+
-`BEST_EFFORT` (default): Recommended for most use cases. Patches make standard asyncio patterns work correctly with minimal overhead.
195
+
-`STRICT`: Use in CI/testing to enforce determinism and catch violations early.
196
+
-`OFF`: Use only if you're certain all code uses `ctx.*` methods exclusively and want absolute zero overhead.
190
197
191
-
When not to use it:
192
-
- Production environments (prefer `OFF` for zero overhead)
193
-
- Performance‑sensitive local loops (e.g., microbenchmarks) unless you are specifically testing detection overhead
198
+
Note: `BEST_EFFORT` is now the default because it makes workflows "just work" with standard asyncio code patterns.
194
199
195
200
Enabling and controlling the detector:
196
201
- Per‑orchestrator registration:
@@ -220,9 +225,11 @@ What warnings/errors look like:
220
225
- Includes violation type, suggested alternative, `workflow_name`, and `instance_id` when available
221
226
222
227
Overhead and performance:
223
-
-`OFF`: zero overhead
224
-
-`BEST_EFFORT`: minimal overhead by default; full detection overhead only when debug is enabled
225
-
-`STRICT`: tracing overhead present; recommended only for testing/enforcement, not for production
228
+
-`OFF`: zero overhead (no patching, no detection)
229
+
-`BEST_EFFORT` (default): minimal overhead from patching; lightweight noop tracer unless debug mode enabled (full detection tracer only when `DAPR_WF_DEBUG=true`)
230
+
-`STRICT`: ~100-200% overhead due to full Python tracing; recommended only for testing/enforcement
231
+
232
+
Note: The patching overhead (module-level function replacement) is minimal. The tracing overhead (sys.settrace) is more significant when full detection is enabled.
226
233
227
234
Limitations and caveats:
228
235
- Direct imports like `from random import random` bind the function and may bypass patching
0 commit comments