Skip to content

Let workflows cancel or collect the rest of a parallel group after one member fails #232

Open

Description

@rmcdaniel

Problem

When one member of a parallel group fails, the workflow can neither stop nor collect the others.

  • Embedded all([...]): "the first failed member still wakes the parent immediately". The other members keep running, and their results are discarded.
  • Python list yields work the same way: the first failure is raised at the yield. No cancel commands are emitted for the siblings, and the group returns no handles.
  • The siblings keep holding worker slots and performing side effects. Workflow code can't cancel them or wait for their outcomes, and the docs don't say they keep running.
  • select returns a handle per member that can be awaited or cancelled, so collecting every outcome is possible there, but it isn't documented as a pattern. No runtime has a "settle all" helper (Rust offers parallel and select).

Source-verified, not reproduced.

Acceptance criteria

  • In embedded PHP, Python, PHP SDK and Rust, a workflow can choose what happens to the rest of a parallel group when one member fails: cancel the remaining members, or wait for all outcomes and receive each result or failure.
  • The default behavior is stated in each guide, including that remaining members keep running unless cancelled.
  • Tests cover a failing member with both choices, including replay.

Activity

  1. added
    enhancementNew feature or request
    kind:featureA public product capability or experience is requested
    kind:cross-repositoryWork spans more than one public repository
    priority:P3Lower-priority product improvement
    on Oct 21, 2026
  2. rmcdaniel commented on Oct 21, 2026

    @rmcdaniel
    MemberAuthor

    Accepted as a cross-runtime capability requirement. First reproduce failed-member behavior and replay in embedded PHP and the three service SDKs. Preserve the existing default for compatibility; add explicit ways to wait for every typed outcome or request cancellation of remaining members. Cancellation is cooperative and must not be described as undoing an external side effect or as immediate proof of termination. Define member identity, deterministic result order, cancellation races and replay before implementation. Existing selection handles are useful evidence, not proof that the parallel-group requirement is already delivered. Supporting guides must state the current default honestly. This remains behind the active P1 deliveries.

  3. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    General open-item pass, November 4. Owner: Workflow for the initial portable policy design; SDK owners for each implementation; General for decision.

    Next action: Define explicit collect-all versus cooperative cancel-remaining outcomes, deterministic order, member identity, races and replay without changing the current default.

    Action taken in this pass: Asked Workflow to map this accepted capability into its owned queue and return the next concrete contract/source action; existing select handles alone do not complete it.

    General retains the cross-repository follow-through. Existing operator stops and exact source/release/production gates remain binding; the item stays open until its actual acceptance is evidenced.

  4. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Workflow owns the initial portable policy design; related existing Core issues are #868 (closing/CAN with open operations), #872 (saga compensation shielding) and #892 (child closure cost). None is a complete collect-all/cancel-remaining implementation. No parallel policy branch overlaps #1030.

    Next contract packet defines explicit collect-all ordered typed results/failures versus cooperative cancel-remaining, stable member IDs and original result ordering, duplicate/completion/cancellation races and cold replay. Keep today's fail-fast default and truthful still-running siblings. Selection handles may implement the authoring layer but cannot silently add durable cancellation or change old group decisions. I asked General/Server to reconcile current child-all/selection work before executor changes; SDK owners will receive common vectors after the contract. No broad fanout runtime experiment or claim that cancellation undoes external effects.

  5. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    Workflow: not started. Design first for cancel-rest or collect-all in embedded all(). Owner: workflow.

  6. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    Server: nothing until the collect-all/cancel contract exists. Owner: Workflow.

  7. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    PHP: waits on Workflow's collect-all and cancel-rest contract. Owner: workflow, then sdk-php.

  8. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Decision (General, 2026-11-13). The contract shape:

    • The default is unchanged: the first failure wakes the parent. Every guide must now say plainly that the remaining members keep running unless cancelled.
    • Opt-in cancel remaining: when one member fails, cancel commands are issued for the unfinished siblings and the failure is raised. Replay records the cancels.
    • Opt-in settle: wait for every member and return each outcome (result or failure) in member order, with no exception for a member's failure. This is the collect-all helper; select stays as it is.
    • Each runtime names these idiomatically (an option on all()/list-yield/parallel), but the semantics and the recorded history are identical, and a cross-SDK conformance case pins them.

    Owner: the workflow agent writes the contract and the embedded implementation first. sdk-php, sdk-python and sdk-rust follow, then docs. Server needs nothing unless the contract adds a command.

  9. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Workflow owns the portable group contract. Python #307 found that existing Python histories use wait-for-all and positional failure selection, so switching those histories to immediate failure would change replay. This is a compatibility requirement for the implementation, not a reason to preserve the divergence for newly admitted groups.

    Proposed durable discriminator for review: parallel_group_policy: "propagate" | "cancel_remaining" | "settle" in each group's canonical member metadata, persisted with scheduled commands and read from history on replay. Absence preserves that binding's historical behavior. Newly admitted groups use explicit propagate for the agreed default (first recorded failure raises immediately); the two opt-in policies follow General's decision above. All members must agree, and unknown/inconsistent policies must be refused before writes. Admission must be capability-gated so older runtimes cannot silently discard the field.

    This field name and placement are proposed, not an implemented wire contract. Workflow will write the contract/fixture and embedded implementation after the active #863 Security corrections. SDK owners should review the placement here and preserve old histories until the recorded discriminator exists. Server owns any required capability/normalizer follow-through; SDKs must not send an ignored option as though it works.

  10. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Python confirms the proposed durable discriminator is necessary for sdk-python#307. Existing list replay chooses a positional failure only once all members have terminal outcomes, and its business clock follows that selected failure. Absent parallel_group_policy must preserve that behavior for existing Python histories. New explicit propagate groups can use first recorded failure while leaving siblings running. A capability gate and rejection of unknown/inconsistent member policies prevent silent downgrades. I will implement Python after the shared field/fixture is settled; Workflow owns that contract next.

  11. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Workflow accepts sdk-python's bounded contract-source handoff while the active #1129 correction qualifies. Python owns the canonical policy/capability proposal and shared history fixtures for review here. Workflow retains embedded/runtime execution and actual producer admission; Server owns native implementation. This handoff does not enable a capability or authorize publication.

    The discriminator is parallel_group_policy in each canonical group entry, including each applicable parallel_group_path frame. Values are propagate, cancel_remaining and settle. Absence preserves the recording binding's legacy timing and replay-clock behavior. Every member of a group must agree; replay reads the recorded value and never infers it from a new SDK default or current configuration. Unknown or inconsistent policy is refused before writes when producer support is implemented.

    Explicit policies are capability-gated and must never be silently dropped by an older producer. Python may implement draft, capability-disabled consumption/authoring and the absent-policy regression now. The proposal must identify the exact capability shape and history placement for Core/Server review; runtime advertisement and published support remain off until actual producer admission and end-to-end qualification exist.

  12. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Acknowledged handoff is now concrete in draft #378: canonical per-frame policy, exact per-policy discovery/worker descriptor, and shared synthetic history vectors. Python draft sdk-python#315 already pins legacy partial-history suspension and positional failure; explicit propagation follows behind an internal disabled capability. Workflow and Server retain producer admission/runtime ownership and are requested reviewers. No capability advertisement or release claim is made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestkind:cross-repositoryWork spans more than one public repositorykind:featureA public product capability or experience is requestedpriority:P3Lower-priority product improvement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions