feat: limit in-flight operations in map/parallel#533
Merged
Conversation
yaythomas
marked this pull request as draft
July 14, 2026 18:28
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
4 times, most recently
from
July 16, 2026 17:26
9f04c8d to
3aa2bdc
Compare
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
3 times, most recently
from
July 22, 2026 22:30
69100b1 to
419d14e
Compare
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
5 times, most recently
from
July 23, 2026 00:34
b144ab0 to
500507e
Compare
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
from
July 23, 2026 00:36
500507e to
e6a45d9
Compare
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
3 times, most recently
from
July 23, 2026 01:04
021cf06 to
c23f02a
Compare
yaythomas
marked this pull request as ready for review
July 23, 2026 01:13
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
2 times, most recently
from
July 23, 2026 07:50
31ab29f to
5077544
Compare
zhongkechen
reviewed
Jul 23, 2026
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
from
July 23, 2026 22:19
5077544 to
24b227b
Compare
yaythomas
marked this pull request as draft
July 23, 2026 23:25
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
from
July 24, 2026 00:18
24b227b to
e37b17a
Compare
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
2 times, most recently
from
July 24, 2026 00:43
bfc5886 to
9643ed8
Compare
yaythomas
marked this pull request as ready for review
July 24, 2026 00:48
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
5 times, most recently
from
July 24, 2026 05:53
488dbb8 to
4106f54
Compare
max_concurrency now bounds in-flight branches via a coordinator loop, the completion decision is recorded in an SDK-owned summary envelope and obeyed on replay, and completion config bounds are validated before the operation starts. Branch threads abandoned by early completion are joined at invocation end, so no SDK-created thread outlives its invocation. BREAKING CHANGE: max_concurrency=0 and min_successful=0 now raise ValidationError (previously unlimited / all-items). min_successful greater than the item count now raises ValidationError. BatchResult omits never-started branches. Large-result summary payloads use the SDK envelope format; custom summary_generator output is stored under the summary key instead of replacing the payload. The internal MapSummaryGenerator and ParallelSummaryGenerator classes are removed; the envelope supersedes them. CompletionConfig.all_completed() now tolerates all failures instead of failing fast.
yaythomas
force-pushed
the
feat/in-flight-max-concurrency
branch
from
July 24, 2026 06:36
4106f54 to
e762f7e
Compare
zhongkechen
approved these changes
Jul 24, 2026
| invocation returns. | ||
| """ | ||
| with self._branch_pools_lock: | ||
| self._branch_pools.append(pool) |
Contributor
There was a problem hiding this comment.
The max_concurrency is no longer bound by the max threads in the pool. Maybe we can consider share the same pool for all concurrency operations.
Contributor
Author
There was a problem hiding this comment.
The pool is still created with max_workers=max_in_flight, where max_in_flight = max_concurrency or len(executables))? And submission is gated on in_flight < max_in_flight.
To make sure I'm understanding, you're proposing one effectively-unbounded cached pool?
While considering this, Python’s ThreadPoolExecutor does not reap idle threads, so the pool retains its high-water thread count until invocation end, and we'd lose isolation. Maybe debugging thread names and dumps get harder too?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of changes:
BREAKING CHANGE -- see the behavior changes section; config validation, result shape, and the large-result summary payload format change in this PR.
max_concurrencypreviously sized the thread pool only, so branches that suspended (invoke, wait, callbacks) released their thread and the next item started immediately. All items were initiated at once regardless of the configured limit.Rework the concurrency executor around a single coordinator loop:
max_concurrencynow bounds in-flight branches. A suspended branch keeps its slot until it reaches a terminal state, matching the JS and Java SDKs. When every in-flight branch is suspended, the parent suspends with the earliest resume timestamp, or indefinitely.TimerSchedulerbackground thread.CompletionPolicy, replacingExecutionCountersand the duplicated logic inBatchResult.type,totalCount,completionReason, the started-branch index set (startedIndexes, orcompletedIndexeswhen smaller), and stats fields (startedCount,successCount,failureCount,status). Replay reads the record and reconstructs the exact live result — same items, same statuses, same reason — instead of re-deriving from child checkpoints. A configuredsummary_generatorno longer produces the payload: its output is stored verbatim under the envelope'ssummarykey for observability and is never read by the SDK, so custom generators get exact replay and cannot displace the record. An exception raised by a custom generator propagates and fails the operation, matching the JS SDK. The envelope is checkpointed as provided; a payload exceeding the checkpoint size limit fails the operation with the service's payload validation error (terminal map failure, no retry loop -- checkpoint API 4xx errors are classified non-retryable). The envelope is written and parsed with plain JSON, independent of any configured serdes. Payloads without the record (checkpoints written by earlier SDK versions) fall back to checkpoint derivation, preserving old behavior. This also makes FLAT-nesting results reconstructable on replay, which checkpoint derivation cannot discriminate.Behavior changes
BatchResultomits never-started branches (matching the JS SDK). Previously every item appeared as STARTED even when an early completion meant it never ran;total_countnow reflects branches that actually started.min_successfulset no longer fail fast on the first failure. The old tolerance check ignoredmin_successfulwhen deciding whether criteria existed, so a single failure stopped scheduling; scheduling now continues past failures untilmin_successfulis reached or all items finish, matching the JS and Java SDKs.max_concurrencyandmin_successfulmust be at least 1,tolerated_failure_countnon-negative,tolerated_failure_percentagewithin 0–100. Previouslymax_concurrency=0silently meant unlimited andmin_successful=0silently meant all items.min_successfulgreater than the item count now raisesValidationErrorat the operation call, matching the Java SDK. Previously it silently degraded to "complete when everything finishes".CompletionConfig.all_completed()now tolerates all failures (tolerated_failure_percentage=100), matching its documentation and the Java SDK. Previously its all-Nonefields selected the fail-fast default, so a single failure stopped scheduling -- the opposite of the factory's name.MapSummaryGeneratorandParallelSummaryGeneratorclasses are removed (not exported from the package root; reachable only by deep import, referenced by no examples or docs, and never invoked on the released large-result path). The envelope writes their fields directly.startedCountnow on map as well as parallel). Output from a customsummary_generatorappears under thesummarykey instead of replacing the whole payload.Testing
aws-durable-execution-sdk-python-conformance-tests, deployed against this branch's SDK): all 9 suites pass with 145/145 validations — map 20/20 (including 9-16 MapLargeResult, which exercises the summarized-replay path, and the early-stop cases for fail-fast, min-successful, and failure-tolerance semantics), parallel 22/22, callback 19/19, child 17/17, invoke 16/16, step 20/20, wait 5/5, wait_for_callback 15/15, wait_for_condition 11/11.Issue #, if available:
Closes #279
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.