Skip to content

Commit 476053b

Browse files
authored
Merge branch 'main' into task-queue-best-practices
2 parents 60aa4b3 + 74e2d48 commit 476053b

8 files changed

Lines changed: 265 additions & 34 deletions

File tree

docs/develop/go/continue-as-new.mdx

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,114 @@
22
id: continue-as-new
33
title: Continue-As-New - Go SDK
44
sidebar_label: Continue-As-New
5+
description: Learn how to use Temporal's Continue-As-New in Go to manage large Event Histories by atomically creating new Workflow Executions with the same Workflow Id and fresh parameters.
56
toc_max_heading_level: 4
67
keywords:
7-
- continue-as-new
8+
- continue-as-new workflow
9+
- restart workflow
10+
- fresh event history
11+
- avoid large event histories
12+
- temporal go continue-as-new
813
tags:
914
- Workflows
1015
- continue-as-new
1116
- Go SDK
1217
- Temporal SDKs
13-
description: Continue-As-New in Temporal allows a Workflow Execution to close and start a new one with the same Workflow Id, new Run Id, and fresh Event History to manage large Event Histories.
1418
---
1519

16-
[Continue-As-New](/workflow-execution/continue-as-new) enables a Workflow Execution to close successfully and create a new Workflow Execution in a single atomic operation if the number of Events in the Event History is becoming too large.
17-
The Workflow Execution spawned from the use of Continue-As-New has the same Workflow Id, a new Run Id, and a fresh Event History and is passed all the appropriate parameters.
20+
This page answers the following questions for Go developers:
21+
22+
- [What is Continue-As-New?](#what)
23+
- [How to Continue-As-New?](#how)
24+
- [When is it right to Continue-as-New?](#when)
25+
- [How to test Continue-as-New?](#how-to-test)
26+
27+
## What is Continue-As-New? {#what}
28+
29+
[Continue-As-New](/workflow-execution/continue-as-new) lets a Workflow Execution close successfully and creates a new Workflow Execution.
30+
You can think of it as a checkpoint when your Workflow gets too long or approaches certain scaling limits.
31+
32+
The new Workflow Execution is in the same [chain](/workflow-execution#workflow-execution-chain); it keeps the same Workflow Id but gets a new Run Id and a fresh Event History.
33+
It also receives your Workflow's usual parameters.
1834

19-
To cause a Workflow Execution to [Continue-As-New](/workflow-execution/continue-as-new), the Workflow API should return the result of the [`NewContinueAsNewError()`](https://pkg.go.dev/go.temporal.io/sdk/workflow#NewContinueAsNewError) function available from the `go.temporal.io/sdk/workflow` package.
35+
## How to Continue-As-New using the Go SDK {#how}
2036

37+
First, design your Workflow parameters so that you can pass in the "current state" when you Continue-As-New into the next Workflow run.
38+
This state is typically set to `None` for the original caller of the Workflow.
39+
40+
<div class="copycode-notice-container">
41+
<a href="https://github.com/temporalio/samples-go/blob/main/safe_message_handler/workflow.go">
42+
View the source code
43+
</a>{' '}
44+
in the context of the rest of the application code.
45+
</div>
2146
```go
22-
func SimpleWorkflow(ctx workflow.Context, value string) error {
23-
...
24-
return workflow.NewContinueAsNewError(ctx, SimpleWorkflow, value)
47+
ClusterManagerInput struct {
48+
State *ClusterManagerState
49+
TestContinueAsNew bool
2550
}
26-
```
2751

28-
To check whether a Workflow Execution was spawned as a result of Continue-As-New, you can check if `workflow.GetInfo(ctx).ContinuedExecutionRunID` is not empty (i.e. `""`).
52+
func newClusterManager(ctx workflow.Context, wfInput ClusterManagerInput) (*ClusterManager, error) {
53+
54+
````
55+
The test hook in the above snippet is covered [below](#how-to-test).
2956

30-
**Notes**
57+
Inside your Workflow, return the [`NewContinueAsNewError`](https://pkg.go.dev/go.temporal.io/sdk/workflow#NewContinueAsNewError) error.
58+
This stops the Workflow right away and starts a new one.
59+
60+
<div class="copycode-notice-container">
61+
<a href="https://github.com/temporalio/samples-go/blob/main/safe_message_handler/workflow.go">
62+
View the source code
63+
</a>{' '}
64+
in the context of the rest of the application code.
65+
</div>
66+
```go
67+
return ClusterManagerResult{}, workflow.NewContinueAsNewError(
68+
ctx,
69+
ClusterManagerWorkflow,
70+
ClusterManagerInput{
71+
State: &cm.state,
72+
TestContinueAsNew: cm.testContinueAsNew,
73+
},
74+
)
75+
````
3176
32-
- To prevent Signal loss, be sure to perform an asynchronous drain on the Signal channel.
33-
Failure to do so can result in buffered Signals being ignored and lost.
34-
- Make sure that the previous Workflow and the Continue-As-New Workflow are referenced by the same alias.
35-
Failure to do so can cause the Workflow to Continue-As-New on an entirely different Workflow.
77+
### Considerations for Workflows with Message Handlers {#with-message-handlers}
3678
37-
:::warning Using Continue-as-New and Updates
79+
If you use Updates or Signals, don't call Continue-as-New from the handlers.
80+
Instead, wait for your handlers to finish in your main Workflow before you return `NewContinueAsNewError`.
81+
See the [`AllHandlersFinished`](message-passing#wait-for-message-handlers) example for guidance.
3882
39-
- Temporal _does not_ support Continue-as-New functionality within Update handlers.
40-
- Complete all handlers _before_ using Continue-as-New.
41-
- Use Continue-as-New from your main Workflow Definition method, just as you would complete or fail a Workflow Execution.
83+
## When is it right to Continue-as-New using the Go SDK? {#when}
4284
43-
:::
85+
Use Continue-as-New when your Workflow might hit [Event History Limits](/workflow-execution/event#event-history).
86+
87+
Temporal tracks your Workflow's progress against these limits to let you know when you should Continue-as-New.
88+
Call `GetInfo(ctx).GetContinueAsNewSuggested()` to check if it's time.
89+
90+
## How to test Continue-as-New using the Go SDK {#how-to-test}
91+
92+
Testing Workflows that naturally Continue-as-New may be time-consuming and resource-intensive.
93+
Instead, add a test hook to check your Workflow's Continue-as-New behavior faster in automated tests.
94+
95+
For example, when `TestContinueAsNew == True`, this sample creates a test-only variable called `maxHistoryLength` and sets it to a small value.
96+
A helper method in the Workflow checks it each time it considers using Continue-as-New:
97+
98+
<div class="copycode-notice-container">
99+
<a href="https://github.com/temporalio/samples-go/blob/main/safe_message_handler/workflow.go">
100+
View the source code
101+
</a>{' '}
102+
in the context of the rest of the application code.
103+
</div>
104+
105+
```go
106+
func (cm *ClusterManager) shouldContinueAsNew(ctx workflow.Context) bool {
107+
if workflow.GetInfo(ctx).GetContinueAsNewSuggested() {
108+
return true
109+
}
110+
if cm.maxHistoryLength > 0 && workflow.GetInfo(ctx).GetCurrentHistoryLength() > cm.maxHistoryLength {
111+
return true
112+
}
113+
return false
114+
}
115+
```

docs/develop/go/core-application.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,8 @@ A large Execution history can thus adversely impact the performance of your Work
569569
Therefore, be mindful of the amount of data you transfer through Activity invocation parameters or Return Values.
570570
Otherwise, no additional limitations exist on Activity implementations.
571571

572-
To spawn an [Activity Execution](/activity-execution), call [`ExecuteActivity()`](https://pkg.go.dev/go.temporal.io/workflow#ExecuteActivity) inside your Workflow Definition.
573-
The API is available from the [`go.temporal.io/sdk/workflow`](https://pkg.go.dev/go.temporal.io/workflow) package.
572+
To spawn an [Activity Execution](/activity-execution), call [`ExecuteActivity()`](https://pkg.go.dev/go.temporal.io/sdk/workflow#ExecuteActivity) inside your Workflow Definition.
573+
The API is available from the [`go.temporal.io/sdk/workflow`](https://pkg.go.dev/go.temporal.io/sdk/workflow) package.
574574
The `ExecuteActivity()` API call requires an instance of `workflow.Context`, the Activity function name, and any variables to be passed to the Activity Execution.
575575
The Activity function name can be provided as a variable object (no quotations) or as a string.
576576
The benefit of passing the actual function object is that the framework can validate the parameters against the Activity Definition.

docs/develop/php/index.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Send messages to read the state of Workflow Executions.
7373
- [How to develop with Updates](/develop/php/message-passing#updates)
7474
- [Message handler patterns](/develop/php/message-passing#message-handler-patterns)
7575
- [Message handler troubleshooting](/develop/php/message-passing#message-handler-troubleshooting)
76+
- [How to develop with Dynamic Handlers](/develop/php/message-passing#dynamic-handler)
7677

7778
## [Interrupt a Workflow feature guide](/develop/php/cancellation)
7879

@@ -98,6 +99,7 @@ Complete Activities asynchronously.
9899

99100
Configure and use the Temporal Observability APIs.
100101

102+
- [How to log from a Workflow](/develop/php/observability#logging)
101103
- [How to use Visibility APIs](/develop/php/observability#visibility)
102104

103105
## [Debugging](/develop/php/debugging)

docs/develop/php/message-passing.mdx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ keywords:
88
- signals
99
- queries
1010
- updates
11+
- dynamic signals
12+
- dynamic queries
13+
- dynamic updates
1114
tags:
1215
- Workflows
1316
- Messages
@@ -839,3 +842,83 @@ When working with Queries, you may encounter these errors:
839842

840843
- **The handler caused the Workflow Task to fail.**
841844
This would happen, for example, if the Query handler blocks the thread for too long without yielding.
845+
846+
## Dynamic components {#dynamic-handler}
847+
848+
Temporal supports Dynamic Queries, Signals, and Updates.
849+
These are unnamed handlers that are invoked if no other statically defined handler with the given name exists.
850+
851+
Dynamic Handlers provide flexibility to handle cases where the names of Queries, Signals, or Updates aren't known at run time.
852+
853+
:::caution
854+
855+
Dynamic Handlers should be used judiciously as a fallback mechanism rather than the primary approach.
856+
Overusing them can lead to maintainability and debugging issues down the line.
857+
858+
Instead, Signals, or Queries should be defined statically whenever possible, with clear names that indicate their purpose.
859+
Use static definitions as the primary way of structuring your Workflows.
860+
861+
Reserve Dynamic Handlers for cases where the handler names are not known at development time and need to be looked up dynamically at runtime.
862+
They are meant to handle edge cases and act as a catch-all, not as the main way of invoking logic.
863+
864+
:::
865+
866+
### How to set a Dynamic Query {#set-a-dynamic-query}
867+
868+
A Dynamic Query in Temporal is a Query method that is invoked dynamically at runtime if no other Query with the same name is registered.
869+
Use [`Workflow::registerDynamicQuery()`](https://php.temporal.io/classes/Temporal-Workflow.html#method_registerDynamicQuery) to set a dynamic Query handler.
870+
871+
The Query Handler parameters must accept a `string` name and [`ValuesInterface`](https://php.temporal.io/classes/Temporal-DataConverter-ValuesInterface.html) for the arguments.
872+
873+
```php
874+
Workflow::registerDynamicQuery(function (string $name, ValuesInterface $arguments): string {
875+
return \sprintf(
876+
'Got query `%s` with %d arguments',
877+
$name,
878+
$arguments->count(),
879+
);
880+
});
881+
```
882+
883+
### How to set a Dynamic Signal {#set-a-dynamic-signal}
884+
885+
A Dynamic Signal in Temporal is a Signal that is invoked dynamically at runtime if no other Signal with the same input is registered.
886+
Use [`Workflow::registerDynamicSignal()`](https://php.temporal.io/classes/Temporal-Workflow.html#method_registerDynamicSignal) to set a dynamic Signal handler.
887+
888+
The Signal Handler parameters must accept a `string` name and [`ValuesInterface`](https://php.temporal.io/classes/Temporal-DataConverter-ValuesInterface.html) for the arguments.
889+
890+
```php
891+
Workflow::registerDynamicSignal(function (string $name, ValuesInterface $arguments): void {
892+
Workflow::getLogger()->info(\sprintf(
893+
'Executed signal `%s` with %d arguments',
894+
$name,
895+
$arguments->count(),
896+
));
897+
});
898+
```
899+
900+
### How to set a Dynamic Update {#set-a-dynamic-update}
901+
902+
A Dynamic Update in Temporal is an Update that is invoked dynamically at runtime if no other Update with the same input is registered.
903+
Use [`Workflow::registerDynamicUpdate()`](https://php.temporal.io/classes/Temporal-Workflow.html#method_registerDynamicUpdate) to set a dynamic Update handler.
904+
905+
The method accepts two arguments:
906+
907+
- Update Handler
908+
- Update Validator (optional) that should throw an exception if the validation fails
909+
910+
Both the Handler and the Validator must accept a `string` name and [`ValuesInterface`](https://php.temporal.io/classes/Temporal-DataConverter-ValuesInterface.html) for the arguments.
911+
912+
```php
913+
Workflow::registerDynamicUpdate(
914+
static fn(string $name, ValuesInterface $arguments): string => \sprintf(
915+
'Got update `%s` with %d arguments',
916+
$name,
917+
$arguments->count(),
918+
),
919+
static fn(string $name, ValuesInterface $arguments) => \str_starts_with(
920+
$name,
921+
'update_',
922+
) or throw new \InvalidArgumentException('Invalid update name'),
923+
);
924+
```

docs/develop/php/observability.mdx

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ toc_max_heading_level: 4
88
keywords:
99
- guide-context
1010
- php
11+
- logging and metrics
1112
- search attribute
1213
- search attributes
1314
- upsert search attributes
@@ -24,9 +25,82 @@ The observability section of the Temporal Developer's guide covers the many ways
2425

2526
This section covers features related to viewing the state of the application, including:
2627

28+
- [Log from a Workflow](#logging)
2729
- [Visibility](#visibility)
2830

29-
## How to use Visibility APIs {#visibility}
31+
## Log from a Workflow {#logging}
32+
33+
Logging enables you to record critical information during code execution.
34+
Loggers create an audit trail and capture information about your Workflow's operation.
35+
An appropriate logging level depends on your specific needs.
36+
During development or troubleshooting, you might use debug or even trace.
37+
In production, you might use info or warn to avoid excessive log volume.
38+
39+
The logger supports the following logging levels:
40+
41+
| Level | Use |
42+
| ------- | --------------------------------------------------------------------------------------------------------- |
43+
| `TRACE` | The most detailed level of logging, used for very fine-grained information. |
44+
| `DEBUG` | Detailed information, typically useful for debugging purposes. |
45+
| `INFO` | General information about the application's operation. |
46+
| `WARN` | Indicates potentially harmful situations or minor issues that don't prevent the application from working. |
47+
| `ERROR` | Indicates error conditions that might still allow the application to continue running. |
48+
49+
The Temporal SDK core normally uses `WARN` as its default logging level.
50+
51+
To get a PSR-3 compatible logger in your Workflow code, use the [`Workflow::getLogger()`](https://php.temporal.io/classes/Temporal-Workflow.html#method_getLogger) method.
52+
53+
```php
54+
use Temporal\Workflow;
55+
56+
#[Workflow\WorkflowInterface]
57+
class MyWorkflow
58+
{
59+
#[Workflow\WorkflowMethod]
60+
public function execute(string $param): \Generator
61+
{
62+
Workflow::getLogger()->info('Workflow started', ['parameter' => $param]);
63+
64+
// Your workflow implementation
65+
66+
Workflow::getLogger()->info('Workflow completed');
67+
return 'Done';
68+
}
69+
}
70+
```
71+
72+
The Workflow logger automatically enriches log context with the current Task Queue name.
73+
74+
Logs in replay mode are omitted unless the [`enableLoggingInReplay`](https://php.temporal.io/classes/Temporal-Worker-WorkerOptions.html#method_withEnableLoggingInReplay) Worker option is set to true.
75+
76+
```php
77+
$factory = WorkerFactory::create();
78+
$worker = $factory->newWorker('your-task-queue', WorkerOptions::new()
79+
->withEnableLoggingInReplay(true)
80+
);
81+
```
82+
83+
### Default Logger
84+
85+
By default, PHP SDK uses a [`StderrLogger`](https://php.temporal.io/classes/Temporal-Worker-Logger-StderrLogger.html) that outputs log messages to the standard error stream.
86+
These messages are automatically captured by RoadRunner and incorporated into its logging system with the INFO level, ensuring proper log collection in both development and production environments.
87+
For more details on RoadRunner's logging capabilities, see the [RoadRunner Logger documentation](https://docs.roadrunner.dev/docs/logging-and-observability/logger).
88+
89+
### How to provide a custom logger {#custom-logger}
90+
91+
You can set a custom PSR-3 compatible logger when creating a Worker:
92+
93+
```php
94+
$myLogger = new MyLogger();
95+
96+
$workerFactory = WorkerFactory::create(converter: $converter);
97+
$worker = $workerFactory->newWorker(
98+
taskQueue: 'my-task-queue',
99+
logger: $myLogger,
100+
);
101+
```
102+
103+
## Visibility APIs {#visibility}
30104

31105
The term Visibility, within the Temporal Platform, refers to the subsystems and APIs that enable an operator to view Workflow Executions that currently exist within a Temporal Service.
32106

docs/develop/python/continue-as-new.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ This stops the Workflow right away and starts a new one.
6565
in the context of the rest of the application code.
6666
</div>
6767
```python
68-
workflow.continue_as_new(
69-
ClusterManagerInput(
70-
state=self.state,
71-
test_continue_as_new=input.test_continue_as_new,
72-
)
68+
workflow.continue_as_new(
69+
ClusterManagerInput(
70+
state=self.state,
71+
test_continue_as_new=input.test_continue_as_new,
7372
)
73+
)
7474
````
7575

7676
### Considerations for Workflows with Message Handlers {#with-message-handlers}

docs/production-deployment/cloud/get-started/api-keys.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,14 +598,14 @@ myClient.Connection.RpcMetadata = new Dictionary<string, string>()
598598
Create an initial `Connection` (for use with `Client`):
599599

600600
```typescript
601-
const connection = await Connection.connect(
601+
const connection = await Connection.connect({
602602
address: <endpoint>,
603603
tls: true,
604604
apiKey: <APIKey>,
605605
metadata: {
606606
'temporal-namespace': <namespace_id>.<account_id>,
607607
},
608-
)
608+
});
609609
const client = new Client({
610610
connection,
611611
namespace: <namespace_id>.<account_id>,
@@ -615,14 +615,14 @@ const client = new Client({
615615
Create an initial Worker `NativeConnection` (for use with `Worker`):
616616

617617
```typescript
618-
const connection = await NativeConnection.connect(
618+
const connection = await NativeConnection.connect({
619619
address: <endpoint>,
620620
tls: true,
621621
apiKey: <APIKey>,
622622
metadata: {
623623
'temporal-namespace': <namespace_id>.<account_id>,
624624
},
625-
)
625+
});
626626
const worker = await Worker.create({
627627
connection,
628628
namespace: <namespace_id>.<account_id>,

0 commit comments

Comments
 (0)