Skip to content

Commit f40ef4e

Browse files
committed
Fix caching of globbed reads
Problem: The result of a globbed read depends not only on the glob pattern but also on the current module URI. However, ResourceManager does not take this into account when caching globbed reads, causing wrong results. Changes: - Cache globbed reads per read expression. This is also how globbed imports are cached, except that import URIs are constant. - Make ReadGlobNode and ImportGlobNode code as similar as possible. - Reduce code duplication by inheriting from AbstractReadNode. - Add some tests.
1 parent dd04e7c commit f40ef4e

File tree

16 files changed

+231
-230
lines changed

16 files changed

+231
-230
lines changed

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/AbstractImportNode.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818
import com.oracle.truffle.api.source.SourceSection;
1919
import java.net.URI;
2020
import org.pkl.core.ast.ExpressionNode;
21+
import org.pkl.core.module.ResolvedModuleKey;
2122

2223
public abstract class AbstractImportNode extends ExpressionNode {
24+
protected final ResolvedModuleKey currentModule;
2325
protected final URI importUri;
2426

25-
AbstractImportNode(SourceSection sourceSection, URI importUri) {
27+
AbstractImportNode(SourceSection sourceSection, ResolvedModuleKey currentModule, URI importUri) {
2628
super(sourceSection);
29+
this.currentModule = currentModule;
2730
this.importUri = importUri;
2831
}
2932

30-
public URI getImportUri() {
33+
public final URI getImportUri() {
3134
return importUri;
3235
}
3336
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/AbstractReadNode.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,33 @@
3030
import org.pkl.core.util.Nullable;
3131

3232
public abstract class AbstractReadNode extends UnaryExpressionNode {
33-
private final ModuleKey moduleKey;
33+
protected final ModuleKey currentModule;
3434

35-
protected AbstractReadNode(SourceSection sourceSection, ModuleKey moduleKey) {
35+
protected AbstractReadNode(SourceSection sourceSection, ModuleKey currentModule) {
3636
super(sourceSection);
37-
this.moduleKey = moduleKey;
37+
this.currentModule = currentModule;
3838
}
3939

4040
@TruffleBoundary
41-
protected @Nullable Object doRead(String resourceUri, VmContext context, Node readNode) {
42-
var resolvedUri = resolveResource(moduleKey, resourceUri);
43-
return context.getResourceManager().read(resolvedUri, readNode).orElse(null);
44-
}
45-
46-
private URI resolveResource(ModuleKey moduleKey, String resourceUri) {
47-
URI parsedUri;
41+
protected final URI parseUri(String resourceUri) {
4842
try {
49-
parsedUri = IoUtils.toUri(resourceUri);
43+
return IoUtils.toUri(resourceUri);
5044
} catch (URISyntaxException e) {
5145
throw exceptionBuilder()
5246
.evalError("invalidResourceUri", resourceUri)
5347
.withHint(e.getReason())
5448
.build();
5549
}
50+
}
5651

52+
@TruffleBoundary
53+
protected final @Nullable Object doRead(String resourceUri, VmContext context, Node readNode) {
54+
var resolvedUri = resolveResource(currentModule, resourceUri);
55+
return context.getResourceManager().read(resolvedUri, readNode).orElse(null);
56+
}
57+
58+
private URI resolveResource(ModuleKey moduleKey, String resourceUri) {
59+
var parsedUri = parseUri(resourceUri);
5760
var context = VmContext.get(this);
5861
URI resolvedUri;
5962
try {

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ImportGlobElementNode.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public final class ImportGlobElementNode extends ExpressionNode {
3636
private final VmLanguage language;
3737
private final ResolvedModuleKey currentModule;
3838

39-
public ImportGlobElementNode(SourceSection sourceSection, VmLanguage language, ResolvedModuleKey currentModule) {
39+
public ImportGlobElementNode(
40+
SourceSection sourceSection, VmLanguage language, ResolvedModuleKey currentModule) {
4041
super(sourceSection);
4142
this.language = language;
4243
this.currentModule = currentModule;
@@ -48,7 +49,7 @@ public Object executeGeneric(VirtualFrame frame) {
4849
var path = (String) VmUtils.getMemberKey(frame);
4950
return importModule(mapping, path);
5051
}
51-
52+
5253
@TruffleBoundary
5354
private VmTyped importModule(VmObjectLike mapping, String path) {
5455
@SuppressWarnings("unchecked")

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ImportGlobNode.java

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,18 @@
3838

3939
@NodeInfo(shortName = "import*")
4040
public class ImportGlobNode extends AbstractImportNode {
41-
private final ResolvedModuleKey currentModule;
4241
private final String globPattern;
4342
private final SharedMemberNode importGlobElementNode;
4443

45-
@CompilationFinal @LateInit private VmMapping importsMapping;
44+
@CompilationFinal @LateInit private VmMapping cachedResult;
4645

4746
public ImportGlobNode(
4847
VmLanguage language,
4948
SourceSection sourceSection,
5049
ResolvedModuleKey currentModule,
5150
URI importUri,
5251
String globPattern) {
53-
super(sourceSection, importUri);
54-
this.currentModule = currentModule;
52+
super(sourceSection, currentModule, importUri);
5553
this.globPattern = globPattern;
5654
importGlobElementNode =
5755
new SharedMemberNode(
@@ -65,42 +63,41 @@ public ImportGlobNode(
6563

6664
@Override
6765
public Object executeGeneric(VirtualFrame frame) {
68-
if (importsMapping == null) {
69-
CompilerDirectives.transferToInterpreterAndInvalidate();
70-
var context = VmContext.get(this);
71-
try {
72-
var moduleKey = context.getModuleResolver().resolve(importUri);
73-
var securityManager = VmContext.get(this).getSecurityManager();
74-
if (!moduleKey.isGlobbable()) {
75-
throw exceptionBuilder()
76-
.evalError("cannotGlobUri", importUri, importUri.getScheme())
77-
.build();
78-
}
79-
var resolvedElements =
80-
GlobResolver.resolveGlob(
81-
securityManager,
82-
moduleKey,
83-
currentModule.getOriginal(),
84-
currentModule.getUri(),
85-
globPattern);
86-
var builder = new VmObjectBuilder();
87-
for (var entry : resolvedElements.entrySet()) {
88-
builder.addEntry(entry.getKey(), importGlobElementNode);
89-
}
90-
importsMapping = builder.toMapping(resolvedElements);
91-
} catch (IOException e) {
92-
throw exceptionBuilder().evalError("ioErrorResolvingGlob", importUri).withCause(e).build();
93-
} catch (SecurityManagerException | HttpClientInitException e) {
94-
throw exceptionBuilder().withCause(e).build();
95-
} catch (PackageLoadError e) {
96-
throw exceptionBuilder().adhocEvalError(e.getMessage()).build();
97-
} catch (InvalidGlobPatternException e) {
66+
if (cachedResult != null) return cachedResult;
67+
68+
CompilerDirectives.transferToInterpreterAndInvalidate();
69+
var context = VmContext.get(this);
70+
try {
71+
var moduleKey = context.getModuleResolver().resolve(importUri);
72+
if (!moduleKey.isGlobbable()) {
9873
throw exceptionBuilder()
99-
.evalError("invalidGlobPattern", globPattern)
100-
.withHint(e.getMessage())
74+
.evalError("cannotGlobUri", importUri, importUri.getScheme())
10175
.build();
10276
}
77+
var resolvedElements =
78+
GlobResolver.resolveGlob(
79+
context.getSecurityManager(),
80+
moduleKey,
81+
currentModule.getOriginal(),
82+
currentModule.getUri(),
83+
globPattern);
84+
var builder = new VmObjectBuilder();
85+
for (var entry : resolvedElements.entrySet()) {
86+
builder.addEntry(entry.getKey(), importGlobElementNode);
87+
}
88+
cachedResult = builder.toMapping(resolvedElements);
89+
return cachedResult;
90+
} catch (IOException e) {
91+
throw exceptionBuilder().evalError("ioErrorResolvingGlob", importUri).withCause(e).build();
92+
} catch (SecurityManagerException | HttpClientInitException e) {
93+
throw exceptionBuilder().withCause(e).build();
94+
} catch (PackageLoadError e) {
95+
throw exceptionBuilder().adhocEvalError(e.getMessage()).build();
96+
} catch (InvalidGlobPatternException e) {
97+
throw exceptionBuilder()
98+
.evalError("invalidGlobPattern", globPattern)
99+
.withHint(e.getMessage())
100+
.build();
103101
}
104-
return importsMapping;
105102
}
106103
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ImportNode.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
@NodeInfo(shortName = "import")
3434
public final class ImportNode extends AbstractImportNode {
3535
private final VmLanguage language;
36-
private final ResolvedModuleKey currentModule;
3736

3837
@CompilationFinal @LateInit private VmTyped importedModule;
3938

@@ -42,9 +41,8 @@ public ImportNode(
4241
SourceSection sourceSection,
4342
ResolvedModuleKey currentModule,
4443
URI importUri) {
45-
super(sourceSection, importUri);
44+
super(sourceSection, currentModule, importUri);
4645
this.language = language;
47-
this.currentModule = currentModule;
4846

4947
assert importUri.isAbsolute();
5048
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobElementNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public Object executeGeneric(VirtualFrame frame) {
3737
var path = (String) VmUtils.getMemberKey(frame);
3838
return readResource(mapping, path);
3939
}
40-
40+
4141
@TruffleBoundary
4242
private Object readResource(VmObjectLike mapping, String path) {
4343
@SuppressWarnings("unchecked")

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobNode.java

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,27 @@
2020
import com.oracle.truffle.api.frame.FrameDescriptor;
2121
import com.oracle.truffle.api.nodes.NodeInfo;
2222
import com.oracle.truffle.api.source.SourceSection;
23-
import java.net.URI;
24-
import java.net.URISyntaxException;
23+
import java.io.IOException;
24+
import org.graalvm.collections.EconomicMap;
25+
import org.pkl.core.SecurityManagerException;
2526
import org.pkl.core.ast.member.SharedMemberNode;
27+
import org.pkl.core.http.HttpClientInitException;
2628
import org.pkl.core.module.ModuleKey;
2729
import org.pkl.core.runtime.VmContext;
2830
import org.pkl.core.runtime.VmLanguage;
31+
import org.pkl.core.runtime.VmMapping;
2932
import org.pkl.core.runtime.VmObjectBuilder;
30-
import org.pkl.core.util.IoUtils;
33+
import org.pkl.core.util.GlobResolver;
34+
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
3135

3236
@NodeInfo(shortName = "read*")
33-
public abstract class ReadGlobNode extends UnaryExpressionNode {
34-
private final ModuleKey currentModule;
37+
public abstract class ReadGlobNode extends AbstractReadNode {
3538
private final SharedMemberNode readGlobElementNode;
39+
private final EconomicMap<String, VmMapping> cachedResults = EconomicMap.create();
3640

3741
protected ReadGlobNode(
3842
VmLanguage language, SourceSection sourceSection, ModuleKey currentModule) {
39-
super(sourceSection);
40-
this.currentModule = currentModule;
43+
super(sourceSection, currentModule);
4144
readGlobElementNode =
4245
new SharedMemberNode(
4346
sourceSection,
@@ -51,30 +54,43 @@ protected ReadGlobNode(
5154
@Specialization
5255
@TruffleBoundary
5356
public Object read(String globPattern) {
54-
var context = VmContext.get(this);
55-
var globUri = toUri(globPattern);
56-
var resolvedElements =
57-
context
58-
.getResourceManager()
59-
.resolveGlob(globUri, currentModule.getUri(), currentModule, this, globPattern);
60-
var builder = new VmObjectBuilder();
61-
for (var entry : resolvedElements.entrySet()) {
62-
builder.addEntry(entry.getKey(), readGlobElementNode);
63-
}
64-
return builder.toMapping(resolvedElements);
65-
}
57+
var cachedResult = cachedResults.get(globPattern);
58+
if (cachedResult != null) return cachedResult;
6659

67-
private URI toUri(String globPattern) {
60+
// use same check as for globbed imports (see AstBuilder)
61+
if (globPattern.startsWith("...")) {
62+
throw exceptionBuilder().evalError("cannotGlobTripleDots").build();
63+
}
64+
var globUri = parseUri(globPattern);
65+
var context = VmContext.get(this);
6866
try {
69-
var globUri = IoUtils.toUri(globPattern);
70-
if (IoUtils.parseTripleDotPath(globUri) != null) {
71-
throw exceptionBuilder().evalError("cannotGlobTripleDots").build();
67+
var resolvedUri = currentModule.resolveUri(globUri);
68+
var reader = context.getResourceManager().getReader(resolvedUri, this);
69+
if (!reader.isGlobbable()) {
70+
throw exceptionBuilder().evalError("cannotGlobUri", globUri, globUri.getScheme()).build();
71+
}
72+
var resolvedElements =
73+
GlobResolver.resolveGlob(
74+
context.getSecurityManager(),
75+
reader,
76+
currentModule,
77+
currentModule.getUri(),
78+
globPattern);
79+
var builder = new VmObjectBuilder();
80+
for (var entry : resolvedElements.entrySet()) {
81+
builder.addEntry(entry.getKey(), readGlobElementNode);
7282
}
73-
return globUri;
74-
} catch (URISyntaxException e) {
83+
cachedResult = builder.toMapping(resolvedElements);
84+
cachedResults.put(globPattern, cachedResult);
85+
return cachedResult;
86+
} catch (IOException e) {
87+
throw exceptionBuilder().evalError("ioErrorResolvingGlob", globPattern).withCause(e).build();
88+
} catch (SecurityManagerException | HttpClientInitException e) {
89+
throw exceptionBuilder().withCause(e).build();
90+
} catch (InvalidGlobPatternException e) {
7591
throw exceptionBuilder()
76-
.evalError("invalidResourceUri", globPattern)
77-
.withHint(e.getReason())
92+
.evalError("invalidGlobPattern", globPattern)
93+
.withHint(e.getMessage())
7894
.build();
7995
}
8096
}

0 commit comments

Comments
 (0)