You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Beginning with Polyglot version 24.2.0, `Context` and `Engine `objects are automatically closed when they are no longer strongly referenced. This feature ensures that unreachable `Context` and `Engine` instances are correcly cleaned up to minimize memory leaks. However, it remains highly recommended to manage the lifecycle of `Context` and `Engine` explicitly, ideally using a try-with-resources statement, rather than relying on garbage collection for this purpose.
8
+
# Automatic Close of Unreachable Context and Engine Objects in Polyglot API
4
9
5
-
### Context Lifecycle Management
10
+
As of Polyglot version 24.2.0, `Context` and `Engine `objects are automatically closed when they are no longer strongly referenced.
11
+
This feature ensures that unreachable `Context` and `Engine` instances are correctly cleaned up to minimize memory leaks.
12
+
However, it remains highly recommended to manage the lifecycle of `Context` and `Engine` explicitly, ideally using a try-with-resources statement, rather than relying on garbage collection for this purpose.
13
+
14
+
## Context Lifecycle Management
6
15
7
16
A `Context` instance is eligible for automatic closure when no strong references to it remain. These references can include the following:
8
17
@@ -11,22 +20,26 @@ A `Context` instance is eligible for automatic closure when no strong references
11
20
* Active polyglot threads or any host thread that has explicitly entered the `Context`. A `Context` that is explicitly [entered](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.html#enter()) will not be garbage-collected until it is explicitly [left](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.html#leave()).
12
21
* Inner `Context` objects, as inner contexts prevent their parent contexts from being closed if they remain reachable.
13
22
14
-
When any of these conditions hold, the `Context` instance remains active and will not be garbage-collected and closed. The presence of active system threads does not prevent a `Context` from being closed.
15
-
23
+
When any of these conditions hold, the `Context` instance remains active and will not be garbage-collected and closed.
24
+
The presence of active system threads does not prevent a `Context` from being closed.
16
25
17
-
###Engine Lifecycle Management
26
+
## Engine Lifecycle Management
18
27
19
-
The `Engine` object follows a similar approach to lifecycle management. An `Engine` will only be closed when it no longer has any strong references to it, such as:
28
+
The `Engine` object follows a similar approach to lifecycle management.
29
+
An `Engine` will only be closed when it no longer has any strong references to it, such as:
20
30
21
31
* Direct references to the `Engine`.
22
32
* Strong references to `Language`, `Instrument`, or `ExecutionListener` instances that were obtained from the `Engine`.
23
33
24
-
Furthermore, an `Engine` cannot be closed if there is an active `Context` within it or if any `Context` objects that uses this `Engine` remain reachable. Similar to `Context` objects, the presence of active system threads does not interfere with the automatic closure of an `Engine`.
34
+
Furthermore, an `Engine` cannot be closed if there is an active `Context` within it or if any `Context` objects that uses this `Engine` remain reachable.
35
+
Similar to `Context` objects, the presence of active system threads does not interfere with the automatic closure of an `Engine`.
25
36
26
-
###TruffleContext and Inner Contexts
37
+
## TruffleContext and Inner Contexts
27
38
28
-
In scenarios where a language implementation creates inner contexts using [TruffleLanguage.Env.newInnerContextBuilder()](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/TruffleLanguage.Env.html#newInnerContextBuilder(java.lang.String...)), both the Polyglot API `Context` representing this inner context and the `TruffleContext` must be unreachable to enable automatic closure. This situation is illustrated in the diagram below, showing an engine with one top-level context and one inner context. Strong references are represented by solid lines, and weak references by dashed lines.
39
+
In scenarios where a language implementation creates inner contexts using [TruffleLanguage.Env.newInnerContextBuilder()](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/TruffleLanguage.Env.html#newInnerContextBuilder(java.lang.String...)), both the Polyglot API `Context` representing this inner context and the `TruffleContext` must be unreachable to enable automatic closure.
40
+
This situation is illustrated in the diagram below, showing an engine with one top-level context and one inner context. Strong references are represented by solid lines, and weak references by dashed lines.
When an inner `Context` is no longer referenced by the embedder, and the corresponding `TruffleContext` instance is no longer referenced by its language implementation, weak references from `PolyglotContext` to `Context` and `TruffleContext` are added to a reference queue. During the next lifecycle event (such as when a new context is created or closed), this queue is processed, and any unreachable context is closed automatically.
44
+
When an inner `Context` is no longer referenced by the embedder, and the corresponding `TruffleContext` instance is no longer referenced by its language implementation, weak references from `PolyglotContext` to `Context` and `TruffleContext` are added to a reference queue.
45
+
During the next lifecycle event (such as when a new context is created or closed), this queue is processed, and any unreachable context is closed automatically.
Since version 25, Truffle has included an automatic deoptimization cycle detection feature, which is a powerful tool
4
-
for identifying existing deoptimization cycles. The goal of this document is to help prevent deoptimization cycles.
5
-
To that end, it describes a number of common patterns that can cause deoptimization cycles and should therefore be avoided.
8
+
# Deoptimization Cycle Patterns
6
9
7
-
### Always Deopt Node
10
+
As of GraalVM for JDK 25, the Truffle framework includes an **automatic deoptimization cycle detection feature**, which is a powerful tool for identifying existing deoptimization cycles.
11
+
The goal of this document is to help prevent deoptimization cycles by describing common patterns that can lead to such cycles, which should be avoided.
8
12
9
-
The following is the simplest pattern that causes a deoptimization cycle:
13
+
### Always Deoptimizing Node
14
+
15
+
One of the simplest patterns that causes a deoptimization cycle is as follows:
10
16
11
17
```java
12
18
classAlwaysDeoptNodeextendsRootNode {
@@ -18,9 +24,9 @@ class AlwaysDeoptNode extends RootNode {
18
24
}
19
25
```
20
26
21
-
When the above code is compiled, it deoptimizes and invalidates on the first execution. The same occurs with each subsequent compilation and execution.
22
-
This is an extreme example, and it's hard to imagine how the code could be useful for anything other than testing purposes,
23
-
but it is a clear cause of unbounded repeated deoptimization—that is, a deoptimization cycle.
27
+
When the above code is compiled, it deoptimizes and invalidates on the first execution.
28
+
The same occurs with each subsequent compilation and execution.
29
+
This is an extreme example, and it is hard to imagine how the code could be useful for anything other than testing purposes, but it is a clear cause of unbounded repeated deoptimization—that is, a deoptimization cycle.
24
30
25
31
### Invalid Argument Profiling Node
26
32
@@ -43,14 +49,15 @@ class InvalidArgumentProfilingNode extends RootNode {
43
49
}
44
50
```
45
51
46
-
The number of deoptimizations and invalidations in the above code is unbounded because the profiled argument can keep changing
47
-
with no limit on the number of these changes. Consequently, there is no bound on the number of deoptimizations.
52
+
The number of deoptimizations and invalidations in the above code is unbounded because the profiled argument can keep changing with no limit on the number of these changes.
53
+
Consequently, there is no bound on the number of deoptimizations.
48
54
49
-
A possible solution to this problem would be to use the `IntValueProfile`, which caches the value only once and switches to a generic state when the value changes.
55
+
A possible solution to this problem would be to use `IntValueProfile`, which caches the value only once and switches to a generic state when the value changes.
50
56
51
57
### Stabilize Late Node
52
58
53
-
The following code does not cause a true deoptimization cycle. It eventually stabilizes, but it does so so late that the deoptimization cycle detection tool might report a deoptimization cycle:
59
+
The following code does not cause a true deoptimization cycle.
60
+
It eventually stabilizes, but stabilization occurs so late that the deoptimization cycle detection tool might still report a cycle:
54
61
55
62
```java
56
63
classStabilizeLateNodeextendsRootNode {
@@ -88,27 +95,29 @@ class StabilizeLateNode extends RootNode {
88
95
}
89
96
```
90
97
91
-
The above code must be executed 20 times with an argument different from `cachedValue` in order for `seenCount`to reach 20 and
92
-
prevent further deoptimization. In theory, the code could be compiled and deoptimized for each value of `seenCount` below 20, i.e., twenty times.
98
+
After 20 consecutive executions where the argument `arg` is different from `cachedValue`, the variable `seenCount`reaches 20. At this point, deoptimizations stop.
99
+
Until then, the code may be compiled and deoptimized once for each `seenCount`value below 20—up to twenty times.
93
100
Since deoptimization cycle detection is enabled by default after 15 compilations, it could be triggered by this late-stabilizing code.
94
101
95
102
The solution to this problem is to ensure that the optimizations stabilize more quickly.
96
103
97
104
### Compilation Final Field of a Non-Constant Object
98
105
99
-
Truffle compilation final fields are a powerful optimization tool and are often used to control speculations. For example, they are used in conditions
100
-
checking whether a certain specialized piece code is valid for the current input. When it is not, and the code is compiled, they cause deoptimization
101
-
and invalidation which allows the code to re-specialize before the next compilation. However, this usage must follow one basic rule:
106
+
Truffle compilation final fields are a powerful optimization tool, often used to control speculations.
107
+
For example, they are used in conditions checking whether a certain specialized piece of code is valid for the current input.
108
+
When it is not, the compiled code is deoptimized and invalidated. This allows the code to re-specialize and recompile with the updated assumption.
109
+
However, this usage must follow one key rule:
102
110
103
111
* The number of possible deoptimizations (and invalidations) caused by the condition involving a compilation final field should be small. As seen in the previous examples, failing to follow this rule can lead to issues.
104
112
105
-
Extra caution must be taken when the object containing the compilation field is not a partial evaluation (PE) constant because, in that case, the field is not compilation-final, and reading it translates to a normal field read. This makes it more difficult to reason about the number of possible deoptimizations caused by a condition involving the field.
113
+
Extra caution must be taken when the object containing the compilation field is not a partial evaluation (PE) constant because, in that case, the field is not compilation-final, and reading it translates to a normal field read.
114
+
This makes it more difficult to reason about the number of possible deoptimizations caused by a condition involving the field.
106
115
107
116
The following examples demonstrate what can happen if this caution is not exercised.
108
117
109
118
#### Example 1: Non-Constant Language Context
110
119
111
-
The following code shows repeated deoptimization caused by the initialization of each fresh language context:
120
+
The following code shows repeated deoptimization caused by the initialization of each new language context:
@@ -146,9 +155,8 @@ class LanguageContextDrivenInitialization extends RootNode {
146
155
}
147
156
```
148
157
149
-
If the `TruffleLanguage.Registration.contextPolicy` of `MyLanguage` is `SHARED`, a new language context is created for each polyglot context used to run the language code. Therefore,
150
-
`languageContext` is not a PE constant and so the number of deoptimizations (and invalidations) driven by the
151
-
`initialized` boolean is unbounded.
158
+
If `TruffleLanguage.Registration.contextPolicy` of `MyLanguage` is `SHARED`, a new language context is created for each polyglot context used to run the language code.
159
+
Therefore, `languageContext` is not a PE constant and so the number of deoptimizations (and invalidations) driven by the `initialized` boolean is unbounded.
152
160
153
161
A possible solution for this problem would be to transfer to the interpreter only when `languageContext` is a PE constant, and to annotate the initialize method with `TruffleBoundary`:
154
162
@@ -232,10 +240,12 @@ class RootNode {
232
240
233
241
The dispatch node has a single call site, and each uninitialized call target must be initialized before the call.
234
242
Initialization is performed in the `getCallTarget` method if the compilation final field `callTarget` on the callee's `RootNode` object is `null`.
235
-
The callee’s root node object is not a PE constant. In fact, the number of root nodes the compiled code can see is theoretically unbounded.
243
+
The callee’s root node object is not a PE constant.
244
+
In fact, the number of root nodes the compiled code can see is theoretically unbounded.
236
245
Initializing a call target causes a deoptimization, so the entire pattern leads to a deoptimization cycle.
237
246
238
-
A possible solution to this problem would be to initialize the call target at parse time or when the function is looked up for the first time. `MyFunction` objects would then store the call targets directly:
247
+
A possible solution to this problem would be to initialize the call target at parse time or when the function is looked up for the first time.
248
+
`MyFunction` objects would then store the call targets directly:
239
249
240
250
```java
241
251
classMyFunction {
@@ -275,7 +285,8 @@ class SkippedExceptionNode extends RootNode {
275
285
}
276
286
```
277
287
278
-
Skipped exceptions are exceptions that always cause a deoptimization. The complete list is as follows:
288
+
Skipped exceptions are exceptions that always cause a deoptimization.
289
+
The complete list is as follows:
279
290
*`UnexpectedResultException`
280
291
*`SlowPathException`
281
292
*`ScopedMemoryAccess$ScopedAccessError`
@@ -290,9 +301,9 @@ Skipped exceptions are exceptions that always cause a deoptimization. The comple
290
301
*`ReadOnlyBufferException`
291
302
*`AssertionError`
292
303
293
-
As you can see from the example, catching the exception does not help. Whenever a skipped exception is thrown in Truffle-compiled code,
294
-
the code is deoptimized and invalidated, and repeated execution of such code leads to a deoptimization cycle.
304
+
As you can see from the example, catching the exception does not help.
305
+
Whenever a skipped exception is thrown in Truffle-compiled code, the code is deoptimized and invalidated, and repeated execution of such code leads to a deoptimization cycle.
295
306
296
-
The solution to this problem is to ensure that a skipped exception is never thrown from compiled code.
297
-
This can be achieved either by throwing the exception behind a `TruffleBoundary` or ensuring, with explicit guards,
298
-
that the error cannot occur in compiled code.
307
+
The solution to this problem is to ensure that skipped exceptions are never thrown from compiled code. You can achieve this by either:
308
+
* Throwing the exception behind `TruffleBoundary`, or
309
+
* Using explicit guards to guarantee that the exception cannot be thrown in compiled code.
0 commit comments