Skip to content

Commit 543d8e1

Browse files
committed
docs: fix parallel docs
Signed-off-by: Michael Gasch <15986659+embano1@users.noreply.github.com>
1 parent 71d71f1 commit 543d8e1

1 file changed

Lines changed: 57 additions & 46 deletions

File tree

docs/core/parallel.md

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727

2828
**Branch** - An individual function within a parallel operation. Each branch executes independently and can succeed or fail without affecting other branches.
2929

30-
**BatchResult** - The result object returned by parallel operations, containing successful results, failed results, and execution metadata.
30+
**BatchResult** - The result object returned by parallel operations. It includes a `BatchItem` for each branch plus counts and completion metadata.
31+
32+
**BatchItem** - A per-branch entry with `index`, `status`, `result`, and `error` (if failed).
3133

3234
**Completion strategy** - Configuration that determines when a parallel operation completes (e.g., all successful, first successful, all completed).
3335

@@ -55,7 +57,7 @@ Use parallel operations to:
5557
- **Independent execution** - Each branch runs in its own child context with isolated state
5658
- **Flexible completion** - Configure when the operation completes (all successful, first successful, etc.)
5759
- **Error isolation** - One branch failing doesn't automatically fail others
58-
- **Result collection** - Automatic collection of successful and failed results
60+
- **Result collection** - Automatic collection of per-branch status, results, and errors
5961
- **Concurrency control** - Limit maximum concurrent branches with `max_concurrency`
6062
- **Checkpointing** - Results are checkpointed as branches complete
6163

@@ -84,14 +86,14 @@ def handler(event: dict, context: DurableContext) -> list[str]:
8486
result: BatchResult[str] = context.parallel([task1, task2, task3])
8587

8688
# Return successful results
87-
return result.successful_results
89+
return result.get_results()
8890
```
8991

9092
When this function runs:
9193
1. All three tasks execute concurrently
9294
2. Each task runs in its own child context
9395
3. Results are collected as tasks complete
94-
4. The `BatchResult` contains all successful results
96+
4. The `BatchResult` contains per-branch status and results; `get_results()` returns successes
9597

9698
[↑ Back to top](#table-of-contents)
9799

@@ -114,15 +116,16 @@ def parallel(
114116
- `config` (optional) - A `ParallelConfig` object to configure concurrency limits, completion criteria, and serialization.
115117

116118
**Returns:** A `BatchResult[T]` object containing:
117-
- `successful_results` - List of results from branches that succeeded
118-
- `failed_results` - List of results from branches that failed
119-
- `total_count` - Total number of branches
120-
- `success_count` - Number of successful branches
121-
- `failure_count` - Number of failed branches
122-
- `status` - Overall status of the parallel operation
119+
- `all` - List of `BatchItem` entries (one per branch) with `index`, `status`, `result`, and `error`
120+
- `get_results()` - List of successful branch results
121+
- `get_errors()` - List of `ErrorObject` entries for failed branches
122+
- `succeeded()` / `failed()` / `started()` - `BatchItem` lists filtered by status
123+
- `total_count`, `success_count`, `failure_count`, `started_count` - Branch counts by status
124+
- `status` - Overall `BatchItemStatus` (FAILED if any branch failed)
123125
- `completion_reason` - Why the operation completed
126+
- `throw_if_error()` - Raises the first branch error, if any
124127

125-
**Raises:** Exceptions are captured per branch and included in `failed_results`. The parallel operation itself doesn't raise unless all branches fail (depending on completion configuration).
128+
**Raises:** Branch exceptions are captured in the `BatchResult`. Call `throw_if_error()` if you want to raise the first failure.
126129

127130
[↑ Back to top](#table-of-contents)
128131

@@ -162,7 +165,7 @@ def handler(event: dict, context: DurableContext) -> dict:
162165
return {
163166
"total": result.total_count,
164167
"successful": result.success_count,
165-
"results": result.successful_results,
168+
"results": result.get_results(),
166169
}
167170
```
168171

@@ -190,22 +193,25 @@ def handler(event: dict, context: DurableContext) -> dict:
190193

191194
return {
192195
# Successful results only
193-
"successful": result.successful_results,
196+
"successful": result.succeeded(),
194197

195198
# Failed results (if any)
196-
"failed": result.failed_results,
199+
"failed": result.failed(),
197200

198201
# Counts
199202
"total_count": result.total_count,
200203
"success_count": result.success_count,
201204
"failure_count": result.failure_count,
205+
"started_count": result.started_count,
202206

203207
# Status information
204208
"status": result.status.value,
205209
"completion_reason": result.completion_reason.value,
206210
}
207211
```
208212

213+
Use `result.succeeded()`, `result.failed()`, or `result.started()` for `BatchItem` lists filtered by status, and `result.throw_if_error()` to raise the first failure when you want exceptions instead of error objects.
214+
209215
### Accessing individual results
210216

211217
Results are ordered by branch index:
@@ -232,19 +238,23 @@ def handler(event: dict, context: DurableContext) -> dict:
232238

233239
result: BatchResult[str] = context.parallel([task_a, task_b, task_c])
234240

241+
results = result.get_results()
242+
235243
# Access results by index
236-
first_result = result.successful_results[0] # "Result A"
237-
second_result = result.successful_results[1] # "Result B"
238-
third_result = result.successful_results[2] # "Result C"
244+
first_result = results[0] # "Result A"
245+
second_result = results[1] # "Result B"
246+
third_result = results[2] # "Result C"
239247

240248
return {
241249
"first": first_result,
242250
"second": second_result,
243251
"third": third_result,
244-
"all": result.successful_results,
252+
"all": results,
245253
}
246254
```
247255

256+
If you need branch-indexed access even when failures occur, iterate `result.all` and match on `item.index`.
257+
248258
[↑ Back to top](#table-of-contents)
249259

250260
## Configuration
@@ -281,11 +291,8 @@ def handler(event: dict, context: DurableContext) -> str:
281291
result: BatchResult[str] = context.parallel(functions, config=config)
282292

283293
# Get the first successful result
284-
first_result = (
285-
result.successful_results[0]
286-
if result.successful_results
287-
else "None"
288-
)
294+
results = result.succeeded()
295+
first_result = results[0] if results else "None"
289296

290297
return f"First successful result: {first_result}"
291298
```
@@ -303,7 +310,7 @@ config = ParallelConfig(max_concurrency=5)
303310

304311
- `CompletionConfig.all_successful()` - Requires all branches to succeed (default)
305312
- `CompletionConfig.first_successful()` - Completes when any branch succeeds
306-
- `CompletionConfig.all_completed()` - Waits for all branches to complete regardless of success/failure
313+
- `CompletionConfig.all_completed()` - Completes when branches finish; check `started_count` if completion criteria are met early
307314
- Custom configuration with specific success/failure thresholds
308315

309316
```python
@@ -320,6 +327,8 @@ config = ParallelConfig(
320327

321328
**item_serdes** - Custom serialization for individual branch results. If not provided, uses JSON serialization.
322329

330+
Note: If completion criteria are met early (min success reached or failure tolerance exceeded), unfinished branches are marked `STARTED` in `result.all` and counted in `started_count`.
331+
323332
[↑ Back to top](#table-of-contents)
324333

325334
## Advanced patterns
@@ -361,8 +370,9 @@ def handler(event: dict, context: DurableContext) -> str:
361370
config=config,
362371
)
363372

364-
if result.successful_results:
365-
return result.successful_results[0]
373+
results = result.get_results()
374+
if results:
375+
return results[0]
366376

367377
return {"error": "All sources failed"}
368378
```
@@ -400,7 +410,7 @@ def handler(event: dict, context: DurableContext) -> dict:
400410
return {
401411
"processed": result.success_count,
402412
"failed": result.failure_count,
403-
"results": result.successful_results,
413+
"results": result.get_results(),
404414
}
405415
```
406416

@@ -440,7 +450,7 @@ def handler(event: dict, context: DurableContext) -> dict:
440450

441451
return {
442452
"status": "partial_success",
443-
"successful": result.successful_results,
453+
"successful": result.get_results(),
444454
"failed_count": result.failure_count,
445455
}
446456
```
@@ -466,7 +476,7 @@ def handler(event: dict, context: DurableContext) -> dict:
466476
task3 = lambda c: c.step(lambda _: "group-a-item-3")
467477

468478
inner_result = ctx.parallel([task1, task2, task3])
469-
return inner_result.successful_results
479+
return inner_result.get_results()
470480

471481
def process_group_b(ctx: DurableContext) -> list:
472482
# Inner parallel operation for group B
@@ -475,14 +485,14 @@ def handler(event: dict, context: DurableContext) -> dict:
475485
task3 = lambda c: c.step(lambda _: "group-b-item-3")
476486

477487
inner_result = ctx.parallel([task1, task2, task3])
478-
return inner_result.successful_results
488+
return inner_result.get_results()
479489

480490
# Outer parallel operation
481491
result: BatchResult[list] = context.parallel([process_group_a, process_group_b])
482492

483493
return {
484494
"groups_processed": result.success_count,
485-
"results": result.successful_results,
495+
"results": result.get_results(),
486496
}
487497
```
488498

@@ -518,15 +528,15 @@ def handler(event: dict, context: DurableContext) -> dict:
518528

519529
functions = [successful_task, failing_task, successful_task]
520530

521-
# Use all_completed to wait for all branches
531+
# Use all_completed to collect per-branch status; check started_count for early completion
522532
config = ParallelConfig(
523533
completion_config=CompletionConfig.all_completed()
524534
)
525535

526536
result: BatchResult[str] = context.parallel(functions, config=config)
527537

528538
return {
529-
"successful": result.successful_results,
539+
"successful": result.succeeded(),
530540
"failed_count": result.failure_count,
531541
"status": result.status.value,
532542
}
@@ -545,14 +555,14 @@ if result.failure_count > 0:
545555
# Some branches failed
546556
return {
547557
"status": "partial_failure",
548-
"successful": result.successful_results,
558+
"successful": result.get_results(),
549559
"failed_count": result.failure_count,
550560
}
551561

552562
# All branches succeeded
553563
return {
554564
"status": "success",
555-
"results": result.successful_results,
565+
"results": result.get_results(),
556566
}
557567
```
558568

@@ -576,19 +586,19 @@ config = ParallelConfig(
576586
# Ignores failures until at least one succeeds
577587
```
578588

579-
**all_completed()** - Waits for all branches regardless of errors:
589+
**all_completed()** - Waits for branches to complete unless completion criteria are met early:
580590
```python
581591
config = ParallelConfig(
582592
completion_config=CompletionConfig.all_completed()
583593
)
584-
# All branches complete, collect both successes and failures
594+
# If completion criteria are met early, remaining branches are marked STARTED
585595
```
586596

587597
[↑ Back to top](#table-of-contents)
588598

589599
## Result ordering
590600

591-
Results in `successful_results` maintain the same order as the input functions:
601+
Results in `get_results()` maintain the same order as the input functions:
592602

593603
```python
594604
from aws_durable_execution_sdk_python import (
@@ -610,18 +620,19 @@ def handler(event: dict, context: DurableContext) -> list[str]:
610620
result = context.parallel(functions)
611621

612622
# Results are in the same order as functions
613-
assert result.successful_results[0] == "First"
614-
assert result.successful_results[1] == "Second"
615-
assert result.successful_results[2] == "Third"
623+
results = result.get_results()
624+
assert results[0] == "First"
625+
assert results[1] == "Second"
626+
assert results[2] == "Third"
616627

617-
return result.successful_results
628+
return results
618629
```
619630

620631
**Important:** Even though branches execute concurrently and may complete in any order, the SDK preserves the original order in the results list. This makes it easy to correlate results with inputs.
621632

622633
### Handling partial results
623634

624-
When some branches fail, `successful_results` only contains results from successful branches, but the order is still preserved relative to the input:
635+
When some branches fail, `succeeded()` only contains results from successful branches, but the order is still preserved relative to the input:
625636

626637
```python
627638
# If function at index 1 fails:
@@ -672,8 +683,8 @@ Choose the right completion strategy for your use case:
672683
- Strict consistency requirements
673684

674685
**all_completed()** - Best for:
675-
- Best-effort operations
676-
- Collecting partial results
686+
- Workflows where you want to observe branch outcomes end-to-end
687+
- Collecting partial results (pair with tolerated failure settings if failures are expected)
677688
- Logging or monitoring tasks
678689

679690
### Checkpointing overhead
@@ -729,7 +740,7 @@ A: No, branch functions must be synchronous. If you need to call async code, use
729740

730741
**Q: What happens if all branches fail?**
731742

732-
A: The behavior depends on your completion configuration. With `all_successful()`, the operation fails. With `all_completed()`, you get a `BatchResult` with all failures in `failed_results`.
743+
A: The behavior depends on your completion configuration. You always get a `BatchResult`; inspect `get_errors()` or `failed()` to see failures, or call `throw_if_error()` to raise the first error.
733744

734745
**Q: Can I cancel running branches?**
735746

0 commit comments

Comments
 (0)