54
54
import java .util .regex .Matcher ;
55
55
import java .util .regex .Pattern ;
56
56
57
+ import org .graalvm .options .OptionCategory ;
58
+ import org .graalvm .options .OptionDescriptors ;
59
+ import org .graalvm .options .OptionKey ;
60
+ import org .graalvm .options .OptionStability ;
61
+ import org .graalvm .options .OptionValues ;
57
62
import org .graalvm .polyglot .Context ;
63
+ import org .graalvm .polyglot .Engine ;
58
64
import org .graalvm .polyglot .PolyglotException ;
59
65
import org .graalvm .polyglot .Source ;
60
66
import org .junit .Assert ;
61
67
import org .junit .Test ;
62
68
import org .junit .function .ThrowingRunnable ;
63
69
64
70
import com .oracle .truffle .api .CallTarget ;
71
+ import com .oracle .truffle .api .Option ;
65
72
import com .oracle .truffle .api .TruffleLanguage ;
66
73
import com .oracle .truffle .api .exception .AbstractTruffleException ;
67
74
import com .oracle .truffle .api .frame .VirtualFrame ;
@@ -89,7 +96,7 @@ public void testTraceSourceCacheFailure() throws Throwable {
89
96
90
97
@ Test
91
98
public void testTraceSourceCacheEviction () throws IOException {
92
- TruffleTestAssumptions .assumeWeakEncapsulation ();
99
+ TruffleTestAssumptions .assumeWeakEncapsulation (); // Can't control GC in the isolate.
93
100
try (ByteArrayOutputStream out = new ByteArrayOutputStream (); Context context = Context .newBuilder ().option ("engine.TraceSourceCache" , "true" ).out (out ).err (out ).build ()) {
94
101
Source auxiliarySource = Source .newBuilder (SourceCacheTestLanguage .ID , "x" , "AuxiliarySource" ).build ();
95
102
String sourceName = "TestSource" ;
@@ -162,10 +169,13 @@ private static void testCommon(String languageId, Map<String, String> options, S
162
169
}
163
170
}
164
171
165
- @ TruffleLanguage .Registration
172
+ @ TruffleLanguage .Registration ( contextPolicy = TruffleLanguage . ContextPolicy . SHARED )
166
173
static class SourceCacheTestLanguage extends TruffleLanguage <TruffleLanguage .Env > {
167
174
static final String ID = TestUtils .getDefaultLanguageId (SourceCacheTestLanguage .class );
168
175
176
+ @ Option (category = OptionCategory .USER , help = "Sharing Group." , stability = OptionStability .STABLE ) //
177
+ static OptionKey <String > SharingGroup = new OptionKey <>("" );
178
+
169
179
@ Override
170
180
protected Env createContext (Env env ) {
171
181
return env ;
@@ -181,6 +191,20 @@ public Object execute(VirtualFrame frame) {
181
191
}
182
192
}.getCallTarget ();
183
193
}
194
+
195
+ @ Override
196
+ protected boolean areOptionsCompatible (OptionValues firstOptions , OptionValues newOptions ) {
197
+ /*
198
+ * Forces creation of a new sharing layer for each context where the option SharingGroup
199
+ * has a different value.
200
+ */
201
+ return firstOptions .get (SharingGroup ).equals (newOptions .get (SharingGroup ));
202
+ }
203
+
204
+ @ Override
205
+ protected OptionDescriptors getOptionDescriptors () {
206
+ return new SourceCacheTestLanguageOptionDescriptors ();
207
+ }
184
208
}
185
209
186
210
@ SuppressWarnings ("serial" )
@@ -205,4 +229,59 @@ protected CallTarget parse(ParsingRequest request) throws Exception {
205
229
throw new ParseException ();
206
230
}
207
231
}
232
+
233
+ @ Test
234
+ public void testSourceCachesCleared () throws IOException {
235
+ TruffleTestAssumptions .assumeWeakEncapsulation (); // Can't control GC in the isolate.
236
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream (); Engine engine = Engine .newBuilder ().option ("engine.TraceSourceCache" , "true" ).out (out ).err (out ).build ()) {
237
+ String sourceName1 ;
238
+ String sourceName2 ;
239
+ Source source1 = Source .newBuilder (SourceCacheTestLanguage .ID , "1" , sourceName1 = "TestSource1" ).build ();
240
+ Source source2 = Source .newBuilder (SourceCacheTestLanguage .ID , "2" , sourceName2 = "TestSource2" ).build ();
241
+ try (Context context1 = Context .newBuilder ().engine (engine ).option (SourceCacheTestLanguage .ID + ".SharingGroup" , "one" ).build ()) {
242
+ String sourceHash1 = String .format ("0x%08x" , context1 .eval (source1 ).asInt ());
243
+ String sourceHash2 = String .format ("0x%08x" , context1 .eval (source2 ).asInt ());
244
+ /*
245
+ * context2 creates a separate sharing layer because of the option SharingGroup and
246
+ * the implementation of the method SourceCacheTestLanguage#areOptionCompatible.
247
+ */
248
+ try (Context context2 = Context .newBuilder ().engine (engine ).option (SourceCacheTestLanguage .ID + ".SharingGroup" , "two" ).build ()) {
249
+ WeakReference <Source > souceRef2 = new WeakReference <>(source2 );
250
+ source2 = null ;
251
+ GCUtils .assertGc ("Source 2 was not collected" , souceRef2 );
252
+ /*
253
+ * The following context2 eval is supposed to clear source2 from context1
254
+ * layer's source cache.
255
+ */
256
+ context2 .eval (source1 );
257
+ WeakReference <Source > souceRef1 = new WeakReference <>(source1 );
258
+ source1 = null ;
259
+ GCUtils .assertGc ("Source 1 was not collected" , souceRef1 );
260
+ /*
261
+ * The following context2 close is supposed to clear source1 both from context1
262
+ * layer's source cache and from context2 layer's source cache.
263
+ */
264
+ }
265
+ List <String > logs = new ArrayList <>();
266
+ forEachLog (out .toByteArray (), (matcher ) -> {
267
+ String logType = matcher .group (1 );
268
+ String suffix ;
269
+ String loggedHash = matcher .group (2 );
270
+ String loggedName = matcher .group (3 );
271
+ if (sourceName1 .equals (loggedName )) {
272
+ Assert .assertEquals (sourceHash1 , loggedHash );
273
+ suffix = "1" ;
274
+ } else if (sourceName2 .equals (loggedName )) {
275
+ Assert .assertEquals (sourceHash2 , loggedHash );
276
+ suffix = "2" ;
277
+ } else {
278
+ suffix = "Unknown" ;
279
+ }
280
+ logs .add (logType + suffix );
281
+ });
282
+ String [] expectedSequence = new String []{"miss1" , "miss2" , "evict2" , "miss1" , "evict1" , "evict1" };
283
+ Assert .assertArrayEquals (expectedSequence , logs .toArray ());
284
+ }
285
+ }
286
+ }
208
287
}
0 commit comments