@@ -65,6 +65,7 @@ informal introduction to the features and their implementation.
65
65
- [ Asyncio Cancellation] ( #asyncio-cancellation )
66
66
- [ Workflow Utilities] ( #workflow-utilities )
67
67
- [ Exceptions] ( #exceptions )
68
+ - [ Signal and update handlers] ( #signal-and-update-handlers )
68
69
- [ External Workflows] ( #external-workflows )
69
70
- [ Testing] ( #testing )
70
71
- [ Automatic Time Skipping] ( #automatic-time-skipping )
@@ -581,28 +582,35 @@ Here are the decorators that can be applied:
581
582
* The purpose of this decorator is to allow operations involving workflow arguments to be performed in the ` __init__ `
582
583
method, before any signal or update handler has a chance to execute.
583
584
* ` @workflow.signal ` - Defines a method as a signal
584
- * Can be defined on an ` async ` or non-` async ` function at any hierarchy depth, but if decorated method is overridden,
585
- the override must also be decorated
586
- * The method's arguments are the signal's arguments
587
- * Can have a ` name ` param to customize the signal name, otherwise it defaults to the unqualified method name
585
+ * Can be defined on an ` async ` or non-` async ` method at any point in the class hierarchy, but if the decorated method
586
+ is overridden, then the override must also be decorated.
587
+ * The method's arguments are the signal's arguments.
588
+ * Return value is ignored.
589
+ * May mutate workflow state, and make calls to other workflow APIs like starting activities, etc.
590
+ * Can have a ` name ` param to customize the signal name, otherwise it defaults to the unqualified method name.
588
591
* Can have ` dynamic=True ` which means all otherwise unhandled signals fall through to this. If present, cannot have
589
592
` name ` argument, and method parameters must be ` self ` , a string signal name, and a
590
593
` Sequence[temporalio.common.RawValue] ` .
591
594
* Non-dynamic method can only have positional arguments. Best practice is to only take a single argument that is an
592
595
object/dataclass of fields that can be added to as needed.
593
- * Return value is ignored
594
- * ` @workflow.query ` - Defines a method as a query
595
- * All the same constraints as ` @workflow.signal ` but should return a value
596
- * Should not be ` async `
597
- * Temporal queries should never mutate anything in the workflow or call any calls that would mutate the workflow
596
+ * See [ Signal and update handlers] ( #signal-and-update-handlers ) below
598
597
* ` @workflow.update ` - Defines a method as an update
599
- * May both accept as input and return a value
598
+ * Can be defined on an ` async ` or non-` async ` method at any point in the class hierarchy, but if the decorated method
599
+ is overridden, then the override must also be decorated.
600
+ * May accept input and return a value
601
+ * The method's arguments are the update's arguments.
600
602
* May be ` async ` or non-` async `
601
603
* May mutate workflow state, and make calls to other workflow APIs like starting activities, etc.
602
- * Also accepts the ` name ` and ` dynamic ` parameters like signals and queries , with the same semantics.
604
+ * Also accepts the ` name ` and ` dynamic ` parameters like signal , with the same semantics.
603
605
* Update handlers may optionally define a validator method by decorating it with ` @update_handler_method.validator ` .
604
606
To reject an update before any events are written to history, throw an exception in a validator. Validators cannot
605
607
be ` async ` , cannot mutate workflow state, and return nothing.
608
+ * See [ Signal and update handlers] ( #signal-and-update-handlers ) below
609
+ * ` @workflow.query ` - Defines a method as a query
610
+ * Should return a value
611
+ * Should not be ` async `
612
+ * Temporal queries should never mutate anything in the workflow or call any calls that would mutate the workflow
613
+ * Also accepts the ` name ` and ` dynamic ` parameters like signal and update, with the same semantics.
606
614
607
615
#### Running
608
616
@@ -705,9 +713,15 @@ deterministic:
705
713
706
714
#### Asyncio Cancellation
707
715
708
- Cancellation is done the same way as ` asyncio ` . Specifically, a task can be requested to be cancelled but does not
709
- necessarily have to respect that cancellation immediately. This also means that ` asyncio.shield() ` can be used to
710
- protect against cancellation. The following tasks, when cancelled, perform a Temporal cancellation:
716
+ Cancellation is done using ` asyncio ` [ task cancellation] ( https://docs.python.org/3/library/asyncio-task.html#task-cancellation ) .
717
+ This means that tasks are requested to be cancelled but can catch the
718
+ [ ` asyncio.CancelledError ` ] ( https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError ) , thus
719
+ allowing them to perform some cleanup before allowing the cancellation to proceed (i.e. re-raising the error), or to
720
+ deny the cancellation entirely. It also means that
721
+ [ ` asyncio.shield() ` ] ( https://docs.python.org/3/library/asyncio-task.html#shielding-from-cancellation ) can be used to
722
+ protect tasks against cancellation.
723
+
724
+ The following tasks, when cancelled, perform a Temporal cancellation:
711
725
712
726
* Activities - when the task executing an activity is cancelled, a cancellation request is sent to the activity
713
727
* Child workflows - when the task starting or executing a child workflow is cancelled, a cancellation request is sent to
@@ -746,6 +760,8 @@ While running in a workflow, in addition to features documented elsewhere, the f
746
760
be marked non-retryable or include details as needed.
747
761
* Other exceptions that come from activity execution, child execution, cancellation, etc are already instances of
748
762
` FailureError ` and will fail the workflow when uncaught.
763
+ * Update handlers are special: an instance of ` temporalio.exceptions.FailureError ` raised in an update handler will fail
764
+ the update instead of failing the workflow.
749
765
* All other exceptions fail the "workflow task" which means the workflow will continually retry until the workflow is
750
766
fixed. This is helpful for bad code or other non-predictable exceptions. To actually fail the workflow, use an
751
767
` ApplicationError ` as mentioned above.
@@ -757,6 +773,26 @@ cause every exception to fail the workflow instead of the task. Also, as a speci
757
773
` temporalio.workflow.NondeterminismError ` (or any superclass of it) is set, non-deterministic exceptions will fail the
758
774
workflow. WARNING: These settings are experimental.
759
775
776
+ #### Signal and update handlers
777
+
778
+ Signal and update handlers are defined using decorated methods as shown in the example [ above] ( #definition ) . Client code
779
+ sends signals and updates using ` workflow_handle.signal ` , ` workflow_handle.execute_update ` , or
780
+ ` workflow_handle.start_update ` . When the workflow receives one of these requests, it starts an ` asyncio.Task ` executing
781
+ the corresponding handler method with the argument(s) from the request.
782
+
783
+ The handler methods may be ` async def ` and can do all the async operations described above (e.g. invoking activities and
784
+ child workflows, and waiting on timers and conditions). Notice that this means that handler tasks will be executing
785
+ concurrently with respect to each other and the main workflow task. Use
786
+ [ asyncio.Lock] ( https://docs.python.org/3/library/asyncio-sync.html#lock ) and
787
+ [ asyncio.Semaphore] ( https://docs.python.org/3/library/asyncio-sync.html#semaphore ) if necessary.
788
+
789
+ Your main workflow task may finish as a result of successful completion, cancellation, continue-as-new, or failure. You
790
+ should ensure that all in-progress signal and update handler tasks have finished before this happens; if you do not, you
791
+ will see a warning (the warning can be disabled via the ` workflow.signal ` /` workflow.update ` decorators). One way to
792
+ ensure that handler tasks have finished is to wait on the ` workflow.all_handlers_finished ` condition:
793
+ ``` python
794
+ await workflow.wait_condition(lambda : workflow.all_handlers_finished())
795
+ ```
760
796
#### External Workflows
761
797
762
798
* ` workflow.get_external_workflow_handle() ` inside a workflow returns a handle to interact with another workflow
0 commit comments