Skip to content

Child workflow signaling#290

Merged
rmcdaniel merged 5 commits into
masterfrom
feature/child-workflow-signaling
Dec 1, 2025
Merged

Child workflow signaling#290
rmcdaniel merged 5 commits into
masterfrom
feature/child-workflow-signaling

Conversation

@rmcdaniel
Copy link
Copy Markdown
Member

This PR introduces a safe mechanism for parent workflows to signal their children through two new features:

1. ChildWorkflowHandle Class

A new ChildWorkflowHandle class that wraps a child workflow and provides context-safe signaling:

  • Context Preservation: Automatically saves and restores parent workflow context when forwarding signals to children
  • Replay Safety: Skips re-signaling during workflow replay to avoid duplicate signals
  • Simple API: Uses __call() magic method to forward any signal method to the child

2. Workflow Methods: child() and children()

Two new methods added to the base Workflow class:

  • child(): Returns a ChildWorkflowHandle for the most recently created child workflow at the current execution index
  • children(): Returns an array of ChildWorkflowHandle instances for all child workflows at the current execution index

Both methods are deterministic - they filter children by parent_index <= $this->index, ensuring consistent results during workflow replay.

Usage Examples

Example 1: Parent Directly Signals Child

public function execute()
{
    // Start child (don't yield yet)
    $childPromise = ChildWorkflowStub::make(ChildWorkflow::class, 'arg');
    
    // Get handle and signal child
    $childHandle = $this->child();
    $childHandle->approve('approved');
    
    // Wait for child to complete
    $result = yield $childPromise;
    return $result;
}

Example 2: Parent Forwards External Signal to Child

class ParentWorkflow extends Workflow
{
    private bool $receivedSignal = false;
    private ?string $signalData = null;

    #[SignalMethod]
    public function externalSignal(string $data): void
    {
        $this->receivedSignal = true;
        $this->signalData = $data;
    }

    public function execute()
    {
        // Start child
        $childPromise = ChildWorkflowStub::make(ChildWorkflow::class);
        
        // Get child handle (called once in execute, saved to local variable)
        $childHandle = $this->child();
        
        // Wait for external signal
        yield WorkflowStub::await(fn () => $this->receivedSignal);
        
        // Forward signal to child using saved handle
        if ($childHandle && $this->signalData) {
            $childHandle->processData($this->signalData);
        }
        
        // Wait for child completion
        $result = yield $childPromise;
        return $result;
    }
}

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (57b9c08) to head (bc4feec).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##              master      #290   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity       316       323    +7     
===========================================
  Files             46        47    +1     
  Lines           1327      1355   +28     
===========================================
+ Hits            1327      1355   +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This comment was marked as resolved.

@rmcdaniel rmcdaniel merged commit fad469b into master Dec 1, 2025
3 checks passed
@rmcdaniel rmcdaniel deleted the feature/child-workflow-signaling branch December 1, 2025 01:03
rmcdaniel pushed a commit that referenced this pull request Apr 17, 2026
…tional

The v2 section of config/workflows.php used bare env() calls for
WORKFLOW_V2_NAMESPACE, the three WORKFLOW_V2_*_COMPATIBILITY keys, and
the two WORKFLOW_V2_HISTORY_EXPORT_SIGNING_KEY keys. That reads like
required configuration; the runtime already tolerates null for all six
(WorkerCompatibility, HistoryExport signing, and namespace scoping each
treat null as "disabled"). Make that explicit so fresh installs do not
appear broken.

- Pass null as the second env() argument on all six keys so the
  default behavior is documented at the config site, not implicit.
- Add inline comments explaining the "null = disabled" semantics and
  pointing to when each key becomes useful (multi-namespace isolation,
  pinned worker builds, authenticated history exports).
- Add a new WorkflowsConfigTest case that unsets those six env vars
  and asserts the config still loads with null values and retains the
  defaulted numeric/string fallbacks (heartbeat_ttl_seconds, queue
  dispatch mode, guardrails boot mode).

Also update the avro data set in CodecIndependentHelpersTest to use
serializeWithCodec/unserializeWithCodec: __callStatic('unserialize')
sniffs the blob by first byte and cannot auto-detect binary Avro. The
codec-explicit path is how persistence layers round-trip Avro blobs in
production.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants