• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

temporalio / sdk-java / #349

03 Aug 2026 05:42AM UTC coverage: 68.221% (+0.08%) from 68.143%
#349

push

github

web-flow
Add Standalone Activities to Temporal Nexus Operation Handler (#2918)

* Enable Nexus activity operations without regressing workflow updates

Compose standalone activity support with the workflow-update Nexus model already present on master. Shared token, callback, link, client, and cancellation paths retain both operation families.

Constraint: Preserve the workflow-update token and API contracts from master
Rejected: Choose one conflict side | each side would drop a supported Nexus operation family
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep activity execution at token type 2 and workflow update at token type 3
Tested: Focused temporal-sdk token, link, invoker, client, async activity, and cancellation tests
Not-tested: Full repository suite and real-server-only standalone activity cases

* Make sure we test attaching

* Make sure to handle ActivityAlreadyStartedException

* Add test with SANO

* Bump test server

7220 of 12610 branches covered (57.26%)

Branch coverage included in aggregate %.

182 of 283 new or added lines in 15 files covered. (64.31%)

13 existing lines in 3 files now uncovered.

29918 of 41828 relevant lines covered (71.53%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.91
/temporal-sdk/src/main/java/io/temporal/internal/sync/CancellationScopeImpl.java
1
package io.temporal.internal.sync;
2

3
import io.temporal.workflow.*;
4
import java.util.*;
5

6
class CancellationScopeImpl implements CancellationScope {
7

8
  private static final ThreadLocal<Deque<CancellationScopeImpl>> scopeStack =
1✔
9
      ThreadLocal.withInitial(ArrayDeque::new);
1✔
10
  private boolean detached;
11
  private CompletablePromise<String> cancellationPromise;
12

13
  static CancellationScopeImpl current() {
14
    if (scopeStack.get().isEmpty()) {
1!
15
      throw new IllegalStateException("Cannot be called by non workflow thread");
×
16
    }
17
    return scopeStack.get().peekFirst();
1✔
18
  }
19

20
  private static void pushCurrent(CancellationScopeImpl scope) {
21
    scopeStack.get().addFirst(scope);
1✔
22
  }
1✔
23

24
  private static void popCurrent(CancellationScopeImpl expected) {
25
    CancellationScopeImpl current = scopeStack.get().pollFirst();
1✔
26
    if (current != expected) {
1!
27
      throw new Error("Unexpected scope");
×
28
    }
29
    if (!current.detached && current.cancellationPromise == null && current.children.isEmpty()) {
1✔
30
      current.parent.removeChild(current);
1✔
31
    }
32
  }
1✔
33

34
  private final Runnable runnable;
35
  private CancellationScopeImpl parent;
36
  // We use a LinkedHashSet because we will iterate through the children, so we need to keep a
37
  // deterministic order.
38
  private final Set<CancellationScopeImpl> children;
39

40
  /**
41
   * When disconnected scope has no parent and thus doesn't receive cancellation requests from it.
42
   */
43
  private boolean cancelRequested;
44

45
  private String reason;
46

47
  CancellationScopeImpl(
48
      boolean ignoreParentCancellation, boolean deterministicOrder, Runnable runnable) {
49
    this(ignoreParentCancellation, deterministicOrder, runnable, current());
1✔
50
  }
1✔
51

52
  CancellationScopeImpl(
53
      boolean detached,
54
      boolean deterministicOrder,
55
      Runnable runnable,
56
      CancellationScopeImpl parent) {
1✔
57
    this.detached = detached;
1✔
58
    this.runnable = runnable;
1✔
59
    if (deterministicOrder) {
1✔
60
      this.children = new LinkedHashSet<>();
1✔
61
    } else {
62
      this.children = new HashSet<>();
1✔
63
    }
64
    setParent(parent);
1✔
65
  }
1✔
66

67
  public CancellationScopeImpl(
68
      boolean ignoreParentCancellation,
69
      boolean deterministicOrder,
70
      Functions.Proc1<CancellationScope> proc) {
1✔
71
    this.detached = ignoreParentCancellation;
1✔
72
    this.runnable = () -> proc.apply(this);
1✔
73
    if (deterministicOrder) {
1!
74
      this.children = new LinkedHashSet<>();
×
75
    } else {
76
      this.children = new HashSet<>();
1✔
77
    }
78
    setParent(current());
1✔
79
  }
1✔
80

81
  private void setParent(CancellationScopeImpl parent) {
82
    if (parent == null) {
1✔
83
      detached = true;
1✔
84
      return;
1✔
85
    }
86
    if (!detached) {
1✔
87
      this.parent = parent;
1✔
88
      parent.addChild(this);
1✔
89
      if (parent.isCancelRequested()) {
1!
UNCOV
90
        cancel(parent.getCancellationReason());
×
91
      }
92
    }
93
  }
1✔
94

95
  @Override
96
  public void run() {
97
    try {
98
      pushCurrent(this);
1✔
99
      runnable.run();
1✔
100
    } finally {
101
      popCurrent(this);
1✔
102
    }
103
  }
1✔
104

105
  @Override
106
  public boolean isDetached() {
107
    return detached;
×
108
  }
109

110
  @Override
111
  public void cancel() {
112
    cancelRequested = true;
1✔
113
    reason = null;
1✔
114
    for (CancellationScopeImpl child : children) {
1✔
115
      child.cancel();
1✔
116
    }
1✔
117
    if (cancellationPromise != null) {
1✔
118
      cancellationPromise.complete(null);
1✔
119
      cancellationPromise = null;
1✔
120
    }
121
  }
1✔
122

123
  @Override
124
  public void cancel(String reason) {
125
    cancelRequested = true;
1✔
126
    this.reason = reason;
1✔
127
    for (CancellationScopeImpl child : children) {
1✔
128
      child.cancel(reason);
1✔
129
    }
1✔
130
    if (cancellationPromise != null) {
1✔
131
      cancellationPromise.complete(reason);
1✔
132
      cancellationPromise = null;
1✔
133
    }
134
  }
1✔
135

136
  @Override
137
  public String getCancellationReason() {
138
    return reason;
1✔
139
  }
140

141
  @Override
142
  public boolean isCancelRequested() {
143
    return cancelRequested;
1✔
144
  }
145

146
  @Override
147
  public CompletablePromise<String> getCancellationRequest() {
148
    if (cancellationPromise == null) {
1✔
149
      cancellationPromise = Workflow.newPromise();
1✔
150
      if (isCancelRequested()) {
1✔
151
        cancellationPromise.complete(getCancellationReason());
1✔
152
      }
153
    }
154
    return cancellationPromise;
1✔
155
  }
156

157
  private void addChild(CancellationScopeImpl scope) {
158
    children.add(scope);
1✔
159
  }
1✔
160

161
  private void removeChild(CancellationScopeImpl scope) {
162
    if (!children.remove(scope)) {
1!
163
      throw new Error("Not a child");
×
164
    }
165
  }
1✔
166
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc