Skip to content

Commit 229ac28

Browse files
authored
Merge branch 'master' into ja/payload-visitor
2 parents 891e9aa + 273f28a commit 229ac28

47 files changed

Lines changed: 1165 additions & 78 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ jobs:
119119
--dynamic-config-value nexusoperation.enableStandalone=true \
120120
--dynamic-config-value history.enableChasm=true \
121121
--dynamic-config-value history.enableCHASMSignalBacklinks=true \
122-
--dynamic-config-value history.enableTransitionHistory=true &
122+
--dynamic-config-value history.enableTransitionHistory=true \
123+
--dynamic-config-value frontend.enableCancelWorkerPollsOnShutdown=true \
124+
--dynamic-config-value frontend.workerCommandsEnabled=true \
125+
--dynamic-config-value system.enableCancelActivityWorkerCommand=true &
123126
sleep 10s
124127
125128
# Can't actually run tests against Java 8 because Mockito 5 requires Java 11+.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- The SDK code is written for Java 8.
1818

1919
## Building and Testing
20-
1. Format the code before committing:
20+
1. Format the code before committing (and don't bother running spotlessCheck, just run apply):
2121
```bash
2222
./gradlew --offline spotlessApply
2323
```

releases/v1.36.0

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# **Highlights**
2+
3+
## Standalone Activities (Public Preview)
4+
5+
Support for Standalone Activities is now in [Public Preview](https://docs.temporal.io/evaluate/development-production-features/release-stages#public-preview).
6+
Telemetry support has been expanded with improved `OpenTracingClientInterceptor` and a new `OpenTracingActivityClientInterceptor`.
7+
8+
## Standalone Nexus Operations (Experimental)
9+
10+
This release adds `NexusClient` interface for interacting with Nexus services outside of workflows. The `NexusServiceClient`
11+
generic interface can be used to execute operations of a specific Nexus service in a type-safe manner.
12+
13+
## GZIP transport-level compression
14+
15+
Client connections now use GZIP transport-level gRPC compression by default.
16+
Use `setGrpcCompression(GrpcCompression.NONE)` when building `ServiceStubsOptions` to disable it.
17+
18+
## Continue-as-New USE_RAMPING_VERSION versioning behaviour
19+
20+
Continue-as-New now supports `USE_RAMPING_VERSION` as an initial versioning behavior.
21+
It pins the workflow to its task queue's Ramping Version at start time, ignoring the workflow's Target Version.
22+
23+
## Activity cancellation without heartbeating
24+
25+
Activities can receive cancellation requests without waiting for a heartbeat response if running against a recent enough version of the server.
26+
You can use the `ActivityExecutionContext.getCancellationToken()` method to detect such cancellations when not using the `heartbeat()` API.
27+
28+
## Additional worker shutdown options
29+
30+
Enabling `WorkerOptions.Builder.setAllowActivityHeartbeatDuringShutdown` removes the limitation on activity heartbeats during
31+
graceful worker shutdown, matching the behavior of other SDKs. Note that this will make it impossible to detect graceful shutdown
32+
via `ActivityWorkerShutdownException`, so if the detection is desired, an alternative method is needed.
33+
34+
`WorkerFactoryOptions.Builder.setShutdownCheckInterval` can be used to speed up worker shutdown in certain testing scenarios.
35+
Changing this setting in production environment is discouraged.
36+
37+
# What's Changed
38+
39+
2026-05-04 - 5a765b17 - CaN USE_RAMPING_VERSION versioning behaviour (#2868)
40+
2026-05-07 - 20afdcb4 - Expose Nexus Endpoint on Nexus Info (#2837)
41+
2026-05-14 - 1e110b28 - Fixed a bug with spaces in WorkflowIds when creating links in the UI. (#2874)
42+
2026-05-18 - b19042bf - Expose ShutdownManager poll interval via WorkerFactoryOptions (#2876)
43+
2026-05-19 - 1386d4b3 - Add banner like other SDKs have (#2877)
44+
2026-05-19 - 73560d3a - remove stale nightly tps omes test (#2879)
45+
2026-05-19 - caba3510 - Upgrade cloud-api to v0.16.0 (#2873)
46+
2026-05-21 - 7a8e6845 - Improve CONTRIBUTING guide and streamline local dev requirements (#2871)
47+
2026-05-22 - 44bb6034 - Shutdown task loss prevention (#2820)
48+
2026-05-22 - d45886cd - Fixed flaky test WorkerFactoryRegistryTest.testRandomOrder (#2886)
49+
2026-05-27 - 187421b0 - Upgrade temporal-api to v1.62.12 (#2892)
50+
2026-05-27 - f71f93b0 - remove dead omes job (#2891)
51+
2026-06-01 - e947cc23 - Add history hints to workflow task started attributes (#2865)
52+
2026-06-02 - 3ed49850 - Add cooldown on dependabot config (#2888)
53+
2026-06-08 - 4d539760 - Wait for MARKER_RECORDED to fire version callback on replay (#2821)
54+
2026-06-10 - 62a7f08a - Fix flaky test `NexusWorkflowTest.testNexusOperationTimeout_AfterStart` (#2908)
55+
2026-06-11 - 27cfa7dc - Add Temporal Nexus Operation Handler (#2842)
56+
2026-06-11 - 5f25aad6 - Standalone operations for Nexus (#2872)
57+
2026-06-12 - 2bc7d9b3 - Use constants for all failure_reason metrics (#2914)
58+
2026-06-12 - c9c4bdc0 - Add tests for temporal-kotlin extension APIs (#2905)
59+
2026-06-15 - 3c2d9382 - Standalone Activities start delay (#2906)
60+
2026-06-15 - 7390e05b - feat(extstore): add initial extstore types (#2900)
61+
2026-06-15 - 78d0fee1 - Add backoff start for CAN (#2913)
62+
2026-06-15 - f3edb105 - Add GZIP compression defaulting to on (#2911)
63+
2026-06-16 - a1b6fff2 - Add OpenTracing interceptor for standalone activities (#2909)
64+
2026-06-16 - aeac5b19 - Grant explicit actions:read to features reusable-workflow caller (#2919)
65+
2026-06-18 - 8d8ca1b5 - Nexus Signal links (#2889)
66+
2026-06-18 - 8e5ee336 - Support Standalone Activity client in temporal-testing (#2916)
67+
2026-06-18 - dae5e0b1 - Add option to let activities heartbeat during worker shutdown (#2903)
68+
2026-06-22 - 85e12a38 - feat(otel): add tracing for startWithUpdate. fixes #2620. (#2925)
69+
2026-06-23 - e9761137 - Implement nexus-based activity cancels (#2917)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.temporal.activity;
2+
3+
import io.temporal.client.ActivityCanceledException;
4+
import io.temporal.common.Experimental;
5+
import java.util.concurrent.CompletableFuture;
6+
7+
/** Token that allows an Activity implementation to observe cancellation requests. */
8+
@Experimental
9+
public interface ActivityCancellationToken {
10+
11+
ActivityCancellationToken NONE =
12+
new ActivityCancellationToken() {
13+
@Override
14+
public boolean isCancellationRequested() {
15+
return false;
16+
}
17+
18+
@Override
19+
public void throwIfCancellationRequested() throws ActivityCanceledException {}
20+
21+
@Override
22+
public CompletableFuture<Void> getCancellationFuture() {
23+
return new CompletableFuture<>();
24+
}
25+
};
26+
27+
/**
28+
* Returns true after cancellation has been requested for this Activity Execution.
29+
*
30+
* <p>If this method returns true, the Activity implementation should stop its work and usually
31+
* call {@link #throwIfCancellationRequested()} to report successful cancellation to Temporal.
32+
*/
33+
boolean isCancellationRequested();
34+
35+
/**
36+
* Throws {@link ActivityCanceledException} if cancellation has been requested for this Activity
37+
* Execution.
38+
*
39+
* <p>Rethrowing this exception from Activity code reports successful cancellation to Temporal.
40+
*/
41+
void throwIfCancellationRequested() throws ActivityCanceledException;
42+
43+
/**
44+
* Future that completes exceptionally with {@link ActivityCanceledException} when cancellation
45+
* has been requested for this Activity Execution.
46+
*
47+
* <p>Activity code should still call {@link #throwIfCancellationRequested()} or otherwise report
48+
* cancellation if it wants the Activity Execution to complete as canceled.
49+
*/
50+
CompletableFuture<Void> getCancellationFuture();
51+
}

temporal-sdk/src/main/java/io/temporal/activity/ActivityExecutionContext.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.uber.m3.tally.Scope;
44
import io.temporal.client.ActivityCompletionException;
55
import io.temporal.client.WorkflowClient;
6+
import io.temporal.common.Experimental;
67
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
78
import io.temporal.worker.WorkerOptions;
89
import java.lang.reflect.Type;
@@ -89,10 +90,17 @@ public interface ActivityExecutionContext {
8990
*/
9091
byte[] getTaskToken();
9192

93+
/**
94+
* Returns a token that can be used by Activity code to observe cancellation requests without
95+
* recording Heartbeats.
96+
*/
97+
@Experimental
98+
ActivityCancellationToken getCancellationToken();
99+
92100
/**
93101
* If this method is called during an Activity Execution then the Activity Execution is not going
94-
* to complete when it's method returns. It is expected to be completed asynchronously using
95-
* {@link io.temporal.client.ActivityCompletionClient}.
102+
* to complete when its method returns. It is expected to be completed asynchronously using {@link
103+
* io.temporal.client.ActivityCompletionClient}.
96104
*
97105
* <p>Async Activity Executions that have {@link #isUseLocalManualCompletion()} set to false will
98106
* not respect the limit defined by {@link WorkerOptions#getMaxConcurrentActivityExecutionSize()}.

temporal-sdk/src/main/java/io/temporal/common/interceptors/ActivityExecutionContextBase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.temporal.common.interceptors;
22

33
import com.uber.m3.tally.Scope;
4+
import io.temporal.activity.ActivityCancellationToken;
45
import io.temporal.activity.ActivityExecutionContext;
56
import io.temporal.activity.ActivityInfo;
67
import io.temporal.activity.ManualActivityCompletionClient;
@@ -52,6 +53,11 @@ public byte[] getTaskToken() {
5253
return next.getTaskToken();
5354
}
5455

56+
@Override
57+
public ActivityCancellationToken getCancellationToken() {
58+
return next.getCancellationToken();
59+
}
60+
5561
@Override
5662
public void doNotCompleteOnReturn() {
5763
next.doNotCompleteOnReturn();
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.temporal.internal.activity;
2+
3+
import io.temporal.activity.ActivityCancellationToken;
4+
import io.temporal.client.ActivityCanceledException;
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.concurrent.CompletionException;
7+
8+
final class ActivityCancellationTokenImpl implements ActivityCancellationToken {
9+
private final CompletableFuture<Void> cancellationFuture = new CompletableFuture<>();
10+
private volatile ActivityCanceledException cancellationException;
11+
12+
@Override
13+
public boolean isCancellationRequested() {
14+
return cancellationException != null;
15+
}
16+
17+
@Override
18+
public void throwIfCancellationRequested() throws ActivityCanceledException {
19+
ActivityCanceledException exception = cancellationException;
20+
if (exception != null) {
21+
throw exception;
22+
}
23+
}
24+
25+
@Override
26+
public CompletableFuture<Void> getCancellationFuture() {
27+
CompletableFuture<Void> result = new CompletableFuture<>();
28+
cancellationFuture.whenComplete(
29+
(ignored, exception) -> {
30+
if (exception == null) {
31+
result.complete(null);
32+
} else {
33+
result.completeExceptionally(unwrapCompletionException(exception));
34+
}
35+
});
36+
return result;
37+
}
38+
39+
synchronized void requestCancel(ActivityCanceledException exception) {
40+
if (cancellationException == null) {
41+
cancellationException = exception;
42+
cancellationFuture.completeExceptionally(exception);
43+
}
44+
}
45+
46+
private static Throwable unwrapCompletionException(Throwable exception) {
47+
return exception instanceof CompletionException && exception.getCause() != null
48+
? exception.getCause()
49+
: exception;
50+
}
51+
}

temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,12 @@
55
public interface ActivityExecutionContextFactory {
66
InternalActivityExecutionContext createContext(
77
ActivityInfoInternal info, Object activity, Scope metricsScope);
8+
9+
/**
10+
* Removes a context for a currently running activity identified by task token and optionally
11+
* requests cancellation.
12+
*
13+
* @return true if the activity was found and cleaned up.
14+
*/
15+
boolean cleanupContext(byte[] taskToken, boolean cancel);
816
}

temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextFactoryImpl.java

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import io.temporal.client.WorkflowClient;
55
import io.temporal.common.converter.DataConverter;
66
import io.temporal.internal.client.external.ManualActivityCompletionClientFactory;
7+
import java.nio.ByteBuffer;
78
import java.time.Duration;
9+
import java.util.Arrays;
810
import java.util.Objects;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
import java.util.concurrent.ConcurrentMap;
913
import java.util.concurrent.ScheduledExecutorService;
1014

1115
public class ActivityExecutionContextFactoryImpl implements ActivityExecutionContextFactory {
@@ -17,6 +21,8 @@ public class ActivityExecutionContextFactoryImpl implements ActivityExecutionCon
1721
private final DataConverter dataConverter;
1822
private final ScheduledExecutorService heartbeatExecutor;
1923
private final ManualActivityCompletionClientFactory manualCompletionClientFactory;
24+
private final ConcurrentMap<ByteBuffer, ActivityExecutionContextImpl> activeContexts =
25+
new ConcurrentHashMap<>();
2026

2127
public ActivityExecutionContextFactoryImpl(
2228
WorkflowClient client,
@@ -42,18 +48,39 @@ public ActivityExecutionContextFactoryImpl(
4248
@Override
4349
public InternalActivityExecutionContext createContext(
4450
ActivityInfoInternal info, Object activity, Scope metricsScope) {
45-
return new ActivityExecutionContextImpl(
46-
client,
47-
namespace,
48-
activity,
49-
info,
50-
dataConverter,
51-
heartbeatExecutor,
52-
manualCompletionClientFactory,
53-
info.getCompletionHandle(),
54-
metricsScope,
55-
identity,
56-
maxHeartbeatThrottleInterval,
57-
defaultHeartbeatThrottleInterval);
51+
ByteBuffer taskToken = taskTokenKey(info.getTaskToken());
52+
ActivityExecutionContextImpl context =
53+
new ActivityExecutionContextImpl(
54+
client,
55+
namespace,
56+
activity,
57+
info,
58+
dataConverter,
59+
heartbeatExecutor,
60+
manualCompletionClientFactory,
61+
info.getCompletionHandle(),
62+
metricsScope,
63+
identity,
64+
maxHeartbeatThrottleInterval,
65+
defaultHeartbeatThrottleInterval,
66+
() -> cleanupContext(info.getTaskToken(), false));
67+
activeContexts.put(taskToken, context);
68+
return context;
69+
}
70+
71+
@Override
72+
public boolean cleanupContext(byte[] taskToken, boolean cancel) {
73+
ActivityExecutionContextImpl context = activeContexts.remove(taskTokenKey(taskToken));
74+
if (context == null) {
75+
return false;
76+
}
77+
if (cancel) {
78+
context.cancelFromWorkerCommand();
79+
}
80+
return true;
81+
}
82+
83+
private static ByteBuffer taskTokenKey(byte[] taskToken) {
84+
return ByteBuffer.wrap(Arrays.copyOf(taskToken, taskToken.length)).asReadOnlyBuffer();
5885
}
5986
}

temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextImpl.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.temporal.internal.activity;
22

33
import com.uber.m3.tally.Scope;
4+
import io.temporal.activity.ActivityCancellationToken;
45
import io.temporal.activity.ActivityExecutionContext;
56
import io.temporal.activity.ActivityInfo;
67
import io.temporal.activity.ManualActivityCompletionClient;
@@ -32,6 +33,7 @@ class ActivityExecutionContextImpl implements InternalActivityExecutionContext {
3233
private final ManualActivityCompletionClientFactory manualCompletionClientFactory;
3334
private final Functions.Proc completionHandle;
3435
private final HeartbeatContext heartbeatContext;
36+
private final Functions.Proc closeCallback;
3537

3638
private final Scope metricsScope;
3739
private final ActivityInfo info;
@@ -51,12 +53,14 @@ class ActivityExecutionContextImpl implements InternalActivityExecutionContext {
5153
Scope metricsScope,
5254
String identity,
5355
Duration maxHeartbeatThrottleInterval,
54-
Duration defaultHeartbeatThrottleInterval) {
56+
Duration defaultHeartbeatThrottleInterval,
57+
Functions.Proc closeCallback) {
5558
this.client = client;
5659
this.activity = activity;
5760
this.metricsScope = metricsScope;
5861
this.info = info;
5962
this.completionHandle = completionHandle;
63+
this.closeCallback = closeCallback;
6064
this.manualCompletionClientFactory = manualCompletionClientFactory;
6165
this.heartbeatContext =
6266
new HeartbeatContextImpl(
@@ -105,6 +109,11 @@ public byte[] getTaskToken() {
105109
return info.getTaskToken();
106110
}
107111

112+
@Override
113+
public ActivityCancellationToken getCancellationToken() {
114+
return heartbeatContext.getCancellationToken();
115+
}
116+
108117
@Override
109118
public void doNotCompleteOnReturn() {
110119
lock.lock();
@@ -170,6 +179,16 @@ public Object getLastHeartbeatValue() {
170179
@Override
171180
public void cancelOutstandingHeartbeat() {
172181
heartbeatContext.cancelOutstandingHeartbeat();
182+
closeCallback.apply();
183+
}
184+
185+
@Override
186+
public void asyncCompletionStarted() {
187+
heartbeatContext.asyncCompletionStarted();
188+
}
189+
190+
void cancelFromWorkerCommand() {
191+
heartbeatContext.cancelFromWorkerCommand();
173192
}
174193

175194
@Override

0 commit comments

Comments
 (0)