Skip to content

Commit ce344dd

Browse files
committed
[GR-58670] Clear polyglot source cache more aggressively.
PullRequest: graal/18976
2 parents 1aec16d + 8dd003d commit ce344dd

File tree

4 files changed

+124
-18
lines changed

4 files changed

+124
-18
lines changed

truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceCacheTest.java

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,21 @@
5454
import java.util.regex.Matcher;
5555
import java.util.regex.Pattern;
5656

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;
5762
import org.graalvm.polyglot.Context;
63+
import org.graalvm.polyglot.Engine;
5864
import org.graalvm.polyglot.PolyglotException;
5965
import org.graalvm.polyglot.Source;
6066
import org.junit.Assert;
6167
import org.junit.Test;
6268
import org.junit.function.ThrowingRunnable;
6369

6470
import com.oracle.truffle.api.CallTarget;
71+
import com.oracle.truffle.api.Option;
6572
import com.oracle.truffle.api.TruffleLanguage;
6673
import com.oracle.truffle.api.exception.AbstractTruffleException;
6774
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -89,7 +96,7 @@ public void testTraceSourceCacheFailure() throws Throwable {
8996

9097
@Test
9198
public void testTraceSourceCacheEviction() throws IOException {
92-
TruffleTestAssumptions.assumeWeakEncapsulation();
99+
TruffleTestAssumptions.assumeWeakEncapsulation(); // Can't control GC in the isolate.
93100
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().option("engine.TraceSourceCache", "true").out(out).err(out).build()) {
94101
Source auxiliarySource = Source.newBuilder(SourceCacheTestLanguage.ID, "x", "AuxiliarySource").build();
95102
String sourceName = "TestSource";
@@ -162,10 +169,13 @@ private static void testCommon(String languageId, Map<String, String> options, S
162169
}
163170
}
164171

165-
@TruffleLanguage.Registration
172+
@TruffleLanguage.Registration(contextPolicy = TruffleLanguage.ContextPolicy.SHARED)
166173
static class SourceCacheTestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
167174
static final String ID = TestUtils.getDefaultLanguageId(SourceCacheTestLanguage.class);
168175

176+
@Option(category = OptionCategory.USER, help = "Sharing Group.", stability = OptionStability.STABLE) //
177+
static OptionKey<String> SharingGroup = new OptionKey<>("");
178+
169179
@Override
170180
protected Env createContext(Env env) {
171181
return env;
@@ -181,6 +191,20 @@ public Object execute(VirtualFrame frame) {
181191
}
182192
}.getCallTarget();
183193
}
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+
}
184208
}
185209

186210
@SuppressWarnings("serial")
@@ -205,4 +229,59 @@ protected CallTarget parse(ParsingRequest request) throws Exception {
205229
throw new ParseException();
206230
}
207231
}
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+
}
208287
}

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import java.util.concurrent.atomic.AtomicBoolean;
7777
import java.util.concurrent.atomic.AtomicLong;
7878
import java.util.concurrent.atomic.AtomicReference;
79+
import java.util.function.Consumer;
7980
import java.util.function.Function;
8081
import java.util.function.Predicate;
8182
import java.util.function.Supplier;
@@ -124,6 +125,7 @@
124125
import com.oracle.truffle.api.nodes.LanguageInfo;
125126
import com.oracle.truffle.api.nodes.Node;
126127
import com.oracle.truffle.api.nodes.RootNode;
128+
import com.oracle.truffle.api.source.Source;
127129
import com.oracle.truffle.api.source.SourceSection;
128130
import com.oracle.truffle.polyglot.PolyglotContextConfig.FileSystemConfig;
129131
import com.oracle.truffle.polyglot.PolyglotContextConfig.PreinitConfig;
@@ -135,7 +137,6 @@
135137
import com.oracle.truffle.polyglot.PolyglotLoggers.EngineLoggerProvider;
136138
import com.oracle.truffle.polyglot.PolyglotLoggers.LoggerCache;
137139
import com.oracle.truffle.polyglot.SystemThread.InstrumentSystemThread;
138-
import java.util.function.Consumer;
139140

140141
/** The implementation of {@link org.graalvm.polyglot.Engine}, stored in the receiver field. */
141142
final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotImpl.VMObject {
@@ -239,6 +240,8 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm
239240

240241
final List<PolyglotSharingLayer> sharedLayers = new ArrayList<>();
241242

243+
private final ReferenceQueue<Source> deadSourcesQueue = new ReferenceQueue<>();
244+
242245
private boolean runtimeInitialized;
243246

244247
AbstractPolyglotHostService polyglotHostService; // effectively final after engine patching
@@ -456,6 +459,10 @@ void freeSharingLayer(PolyglotSharingLayer layer, PolyglotContextImpl context) {
456459
}
457460
}
458461

462+
ReferenceQueue<Source> getDeadSourcesQueue() {
463+
return deadSourcesQueue;
464+
}
465+
459466
void ensureRuntimeInitialized(PolyglotContextImpl context) {
460467
assert Thread.holdsLock(lock);
461468

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ static final class Shared {
113113
int claimedCount;
114114

115115
private Shared(PolyglotEngineImpl engine, ContextPolicy contextPolicy, Map<PolyglotLanguage, OptionValuesImpl> previousLanguageOptions) {
116-
this.sourceCache = new PolyglotSourceCache(TracingSourceCacheListener.createOrNull(engine));
116+
this.sourceCache = new PolyglotSourceCache(engine.getDeadSourcesQueue(), TracingSourceCacheListener.createOrNull(engine));
117117
this.contextPolicy = contextPolicy;
118118
this.instances = new PolyglotLanguageInstance[engine.languageCount];
119119
this.previousLanguageOptions = previousLanguageOptions;
@@ -340,6 +340,7 @@ public void freeSharingLayer(PolyglotContextImpl context) {
340340
assert isClaimed();
341341

342342
shared.claimedCount--;
343+
shared.sourceCache.cleanupStaleEntries();
343344

344345
if (engine.getEngineOptionValues().get(PolyglotEngineOptions.TraceCodeSharing)) {
345346
traceFreeLayer(context);

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSourceCache.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@
5656

5757
final class PolyglotSourceCache {
5858

59-
private final Cache strongCache;
60-
private final Cache weakCache;
59+
private final StrongCache strongCache;
60+
private final WeakCache weakCache;
6161
private SourceCacheListener sourceCacheListener;
6262

63-
PolyglotSourceCache(SourceCacheListener sourceCacheListener) {
63+
PolyglotSourceCache(ReferenceQueue<Source> deadSourcesQueue, TracingSourceCacheListener sourceCacheListener) {
6464
this.sourceCacheListener = sourceCacheListener;
65-
this.weakCache = new WeakCache();
65+
this.weakCache = new WeakCache(deadSourcesQueue);
6666
this.strongCache = new StrongCache();
6767
}
6868

@@ -103,6 +103,10 @@ void listCachedSources(PolyglotImpl polyglot, Collection<Object> source) {
103103
weakCache.listSources(polyglot, source);
104104
}
105105

106+
void cleanupStaleEntries() {
107+
WeakCache.cleanupStaleEntries(weakCache.deadSources, sourceCacheListener);
108+
}
109+
106110
private static CallTarget parseImpl(PolyglotLanguageContext context, String[] argumentNames, Source source) {
107111
validateSource(context, source);
108112
CallTarget parsedTarget = LANGUAGE.parse(context.requireEnv(), source, null, argumentNames);
@@ -256,14 +260,20 @@ void listSources(PolyglotImpl polyglot, Collection<Object> sources) {
256260
private final class WeakCache extends Cache {
257261

258262
private final ConcurrentHashMap<WeakSourceKey, WeakCacheValue> sourceCache = new ConcurrentHashMap<>();
259-
private final ReferenceQueue<Source> deadSources = new ReferenceQueue<>();
263+
private final ReferenceQueue<Source> deadSources;
264+
private final WeakReference<WeakCache> cacheRef;
265+
266+
WeakCache(ReferenceQueue<Source> deadSources) {
267+
this.deadSources = deadSources;
268+
this.cacheRef = new WeakReference<>(this);
269+
}
260270

261271
@Override
262272
CallTarget lookup(PolyglotLanguageContext context, Source source, String[] argumentNames, boolean parse) {
263-
cleanupStaleEntries();
273+
cleanupStaleEntries(deadSources, sourceCacheListener);
264274
Object sourceId = EngineAccessor.SOURCE.getSourceIdentifier(source);
265275
Source sourceValue = EngineAccessor.SOURCE.copySource(source);
266-
WeakSourceKey ref = new WeakSourceKey(new SourceKey(sourceId, argumentNames), source, deadSources);
276+
WeakSourceKey ref = new WeakSourceKey(new SourceKey(sourceId, argumentNames), source, cacheRef, deadSources);
267277
WeakCacheValue value = sourceCache.get(ref);
268278
if (value == null) {
269279
if (parse) {
@@ -308,18 +318,25 @@ boolean isEmpty() {
308318

309319
@Override
310320
void listSources(PolyglotImpl polyglot, Collection<Object> sources) {
311-
cleanupStaleEntries();
321+
cleanupStaleEntries(deadSources, sourceCacheListener);
312322
for (WeakCacheValue value : sourceCache.values()) {
313323
sources.add(PolyglotImpl.getOrCreatePolyglotSource(polyglot, value.source));
314324
}
315325
}
316326

317-
private void cleanupStaleEntries() {
327+
/**
328+
* This is deliberately a static method, because the dead sources queue is
329+
* {@link PolyglotEngineImpl#getDeadSourcesQueue() shared} among all weak caches on a
330+
* particular polyglot engine, and so the elements of {@link WeakCache#deadSources the
331+
* queue} may {@link WeakSourceKey#cacheRef refer} to different weak caches.
332+
*/
333+
private static void cleanupStaleEntries(ReferenceQueue<Source> deadSourcesQueue, SourceCacheListener cacheListener) {
318334
WeakSourceKey sourceRef;
319-
while ((sourceRef = (WeakSourceKey) deadSources.poll()) != null) {
320-
WeakCacheValue value = sourceCache.remove(sourceRef);
321-
if (value != null && sourceCacheListener != null) {
322-
sourceCacheListener.onCacheEvict(value.source, value.target, SourceCacheListener.CacheType.WEAK, value.hits.get());
335+
while ((sourceRef = (WeakSourceKey) deadSourcesQueue.poll()) != null) {
336+
WeakCache cache = sourceRef.cacheRef.get();
337+
WeakCacheValue value = cache != null ? cache.sourceCache.remove(sourceRef) : null;
338+
if (value != null && cacheListener != null) {
339+
cacheListener.onCacheEvict(value.source, value.target, SourceCacheListener.CacheType.WEAK, value.hits.get());
323340
}
324341
}
325342
}
@@ -373,9 +390,11 @@ public boolean equals(Object obj) {
373390
private static final class WeakSourceKey extends WeakReference<Source> {
374391

375392
final SourceKey key;
393+
final WeakReference<WeakCache> cacheRef;
376394

377-
WeakSourceKey(SourceKey key, Source value, ReferenceQueue<? super Source> q) {
395+
WeakSourceKey(SourceKey key, Source value, WeakReference<WeakCache> cacheRef, ReferenceQueue<? super Source> q) {
378396
super(value, q);
397+
this.cacheRef = cacheRef;
379398
this.key = key;
380399
}
381400

0 commit comments

Comments
 (0)