0
# Cancellation System
1
2
Cancellable contexts with hierarchical cancellation propagation, listener notification, and cause tracking for managing the lifecycle of operations.
3
4
## Capabilities
5
6
### Cancellable Context Creation
7
8
Create contexts that can be independently cancelled while also cascading cancellation from their parent. Callers must ensure that cancel() or close() are called to allow garbage collection.
9
10
```java { .api }
11
/**
12
* Create a new context which is independently cancellable and cascades cancellation from its parent.
13
* @return CancellableContext that must be cancelled at some point
14
*/
15
public Context.CancellableContext withCancellation();
16
```
17
18
**Usage Example:**
19
20
```java
21
Context.CancellableContext cancellableContext = Context.current().withCancellation();
22
try {
23
cancellableContext.run(() -> {
24
Context current = Context.current();
25
while (!current.isCancelled()) {
26
// Keep working until cancelled
27
doWork();
28
}
29
});
30
} finally {
31
cancellableContext.cancel(null); // Always cancel to prevent memory leaks
32
}
33
```
34
35
### Cancellation Status Checking
36
37
Check if a context has been cancelled and retrieve the cancellation cause.
38
39
```java { .api }
40
/**
41
* Check if this context is cancelled.
42
* @return true if context is cancelled, false otherwise
43
*/
44
public boolean isCancelled();
45
46
/**
47
* Get the cause of cancellation if the context is cancelled.
48
* @return Throwable cause of cancellation, or null if not cancelled or no cause provided
49
*/
50
public Throwable cancellationCause();
51
```
52
53
**Usage Example:**
54
55
```java
56
Context.CancellableContext cancellableContext = Context.current().withCancellation();
57
58
// Check cancellation status
59
if (cancellableContext.isCancelled()) {
60
Throwable cause = cancellableContext.cancellationCause();
61
if (cause != null) {
62
System.out.println("Cancelled due to: " + cause.getMessage());
63
}
64
return; // Exit early if cancelled
65
}
66
```
67
68
### Context Cancellation
69
70
Cancel a context with an optional cause. It is safe to call cancel() multiple times - only the first call has any effect.
71
72
```java { .api }
73
public static final class CancellableContext extends Context implements Closeable {
74
/**
75
* Cancel this context and optionally provide a cause for the cancellation.
76
* @param cause Optional cause for the cancellation (can be null)
77
* @return true if this call cancelled the context, false if already cancelled
78
*/
79
public boolean cancel(Throwable cause);
80
81
/**
82
* Cleans up this object by calling cancel(null). Equivalent to cancel(null).
83
*/
84
public void close();
85
86
/**
87
* Cancel this context and detach it as the current context.
88
* @param toAttach Context to make current after detaching
89
* @param cause Optional cause for cancellation
90
*/
91
public void detachAndCancel(Context toAttach, Throwable cause);
92
}
93
```
94
95
**Usage Examples:**
96
97
```java
98
// Basic cancellation
99
Context.CancellableContext ctx = Context.current().withCancellation();
100
boolean wasCancelled = ctx.cancel(new RuntimeException("Operation timeout"));
101
102
// Using try-with-resources (automatically calls close())
103
try (Context.CancellableContext ctx = Context.current().withCancellation()) {
104
Context toRestore = ctx.attach();
105
try {
106
performOperation();
107
} finally {
108
ctx.detach(toRestore);
109
}
110
} // Automatically cancelled on close
111
112
// Cancel and detach in one operation
113
Context.CancellableContext ctx = Context.current().withCancellation();
114
Context previous = ctx.attach();
115
try {
116
performOperation();
117
} finally {
118
ctx.detachAndCancel(previous, null);
119
}
120
```
121
122
### Cancellation Listeners
123
124
Add and remove listeners that will be notified when the context becomes cancelled. Listeners are executed on the provided executor.
125
126
```java { .api }
127
/**
128
* Add a listener that will be notified when the context becomes cancelled.
129
* @param cancellationListener The listener to notify on cancellation
130
* @param executor The executor to run the listener on
131
*/
132
public void addListener(Context.CancellationListener cancellationListener, Executor executor);
133
134
/**
135
* Remove a previously added CancellationListener.
136
* @param cancellationListener The listener to remove
137
*/
138
public void removeListener(Context.CancellationListener cancellationListener);
139
140
/**
141
* A listener notified on context cancellation.
142
*/
143
public interface CancellationListener {
144
/**
145
* Notifies that a context was cancelled.
146
* @param context The newly cancelled context
147
*/
148
void cancelled(Context context);
149
}
150
```
151
152
**Usage Example:**
153
154
```java
155
Context.CancellableContext cancellableContext = Context.current().withCancellation();
156
Executor executor = Executors.newSingleThreadExecutor();
157
158
// Create a cancellation listener
159
Context.CancellationListener listener = new Context.CancellationListener() {
160
@Override
161
public void cancelled(Context context) {
162
System.out.println("Context was cancelled!");
163
Throwable cause = context.cancellationCause();
164
if (cause != null) {
165
System.out.println("Cancellation cause: " + cause.getMessage());
166
}
167
// Perform cleanup operations
168
cleanupResources();
169
}
170
};
171
172
// Add the listener
173
cancellableContext.addListener(listener, executor);
174
175
try {
176
// Perform work
177
cancellableContext.run(() -> {
178
doLongRunningWork();
179
});
180
} finally {
181
// Remove listener and cancel context
182
cancellableContext.removeListener(listener);
183
cancellableContext.cancel(null);
184
}
185
```
186
187
### Hierarchical Cancellation
188
189
Child contexts automatically inherit cancellation from their parents. When a parent context is cancelled, all descendant contexts are also cancelled.
190
191
**Usage Example:**
192
193
```java
194
// Create parent cancellable context
195
Context.CancellableContext parentContext = Context.current().withCancellation();
196
197
parentContext.run(() -> {
198
// Create child context within parent scope
199
Context.CancellableContext childContext = Context.current().withCancellation();
200
201
childContext.run(() -> {
202
// This context will be cancelled when either childContext or parentContext is cancelled
203
Context current = Context.current();
204
205
while (!current.isCancelled()) {
206
doWork();
207
}
208
209
// Check which context was cancelled
210
if (childContext.isCancelled()) {
211
System.out.println("Child context was cancelled directly");
212
} else if (parentContext.isCancelled()) {
213
System.out.println("Parent context cancellation cascaded to child");
214
}
215
});
216
217
childContext.cancel(null);
218
});
219
220
parentContext.cancel(null);
221
```
222
223
### Context Attachment for Cancellable Contexts
224
225
CancellableContext uses a surrogate for attachment to prevent external code from accessing cancellation methods.
226
227
```java { .api }
228
/**
229
* Attach this cancellable context. Uses an internal surrogate to prevent
230
* external access to cancellation methods through Context.current().
231
* @return The previously current context
232
*/
233
@Override
234
public Context attach();
235
236
/**
237
* Detach this cancellable context, restoring the previous context.
238
* @param toAttach The context returned by the corresponding attach() call
239
*/
240
@Override
241
public void detach(Context toAttach);
242
```
243
244
**Usage Example:**
245
246
```java
247
Context.CancellableContext cancellableContext = Context.current().withCancellation();
248
249
Context previous = cancellableContext.attach();
250
try {
251
// Context.current() returns a non-cancellable view
252
Context current = Context.current();
253
assert !(current instanceof Context.CancellableContext);
254
255
// But cancellation status is still accessible
256
boolean isCancelled = current.isCancelled();
257
258
} finally {
259
cancellableContext.detach(previous);
260
}
261
```