Skip to content

Commit d88c8bd

Browse files
committed
[GR-69948] Improve error when internal resource cache folder read-only during native-image build.
PullRequest: graal/22206
2 parents 7e6601e + 8285264 commit d88c8bd

File tree

5 files changed

+93
-29
lines changed

5 files changed

+93
-29
lines changed

substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import java.util.function.ToLongFunction;
101101
import java.util.function.UnaryOperator;
102102

103+
import com.oracle.truffle.api.impl.DefaultTruffleRuntime;
103104
import org.graalvm.collections.Pair;
104105
import org.graalvm.nativeimage.AnnotationAccess;
105106
import org.graalvm.nativeimage.ImageSingletons;
@@ -276,7 +277,16 @@ public static boolean isInConfiguration() {
276277

277278
@Override
278279
public void afterRegistration(AfterRegistrationAccess access) {
279-
UserError.guarantee(Truffle.getRuntime() instanceof SubstrateTruffleRuntime, "TruffleFeature requires SubstrateTruffleRuntime");
280+
TruffleRuntime runtime = Truffle.getRuntime();
281+
if (runtime instanceof DefaultTruffleRuntime defaultTruffleRuntime) {
282+
String errorMessage = defaultTruffleRuntime.getFallbackReason();
283+
throw UserError.abort("""
284+
Failed to create the optimized Truffle runtime: %s
285+
Either fix the problem above or enable the fallback Truffle runtime by setting the system property `-Dtruffle.UseFallbackRuntime=true`.
286+
Execution without runtime compilation will negatively impact the guest application performance.
287+
""", errorMessage);
288+
}
289+
UserError.guarantee(runtime instanceof SubstrateTruffleRuntime, "TruffleFeature requires SubstrateTruffleRuntime");
280290
SubstrateTruffleRuntime truffleRuntime = (SubstrateTruffleRuntime) Truffle.getRuntime();
281291
truffleRuntime.resetHosted();
282292
RuntimeCompilationFeature.singleton().setUniverseFactory(new SubstrateTruffleUniverseFactory(truffleRuntime));

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/Truffle.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,12 @@ private static TruffleRuntime createRuntime() throws InternalError {
129129
* This path is taken if a truffle runtime class is directly specified. In such case
130130
* we try to export JVMCI to the module of that class.
131131
*/
132-
maybeExportJVMCITo(runtimeClass);
133-
return (TruffleRuntime) runtimeClass.getDeclaredConstructor().newInstance();
132+
String errorMessage = maybeExportJVMCITo(runtimeClass);
133+
if (errorMessage == null) {
134+
return (TruffleRuntime) runtimeClass.getDeclaredConstructor().newInstance();
135+
} else {
136+
return new DefaultTruffleRuntime(errorMessage);
137+
}
134138
} catch (Throwable e) {
135139
// Fail fast for other errors
136140
throw new InternalError(e);
@@ -166,16 +170,16 @@ private static TruffleRuntime createRuntime() throws InternalError {
166170
return new DefaultTruffleRuntime(reason);
167171
}
168172

169-
private static void maybeExportJVMCITo(Class<?> runtimeClass) throws ReflectiveOperationException {
173+
private static String maybeExportJVMCITo(Class<?> runtimeClass) throws ReflectiveOperationException {
170174
Class<?> modulesSupport;
171175
try {
172176
modulesSupport = Class.forName("com.oracle.truffle.runtime.ModulesSupport");
173177
} catch (ClassNotFoundException e) {
174178
// we ignore if modules support is not available.
175179
// this typically means that the runtime not on the module-path
176-
return;
180+
return null;
177181
}
178-
modulesSupport.getMethod("exportJVMCI", Class.class).invoke(null, runtimeClass);
182+
return (String) modulesSupport.getMethod("exportJVMCI", Class.class).invoke(null, runtimeClass);
179183
}
180184

181185
@SuppressWarnings("deprecation")

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ String getResourceId() {
128128
return resourceId;
129129
}
130130

131+
Path getOwningRoot() {
132+
return owningRoot == null ? null : owningRoot.path();
133+
}
134+
131135
Path getPathOrNull() {
132136
return path;
133137
}
@@ -202,10 +206,10 @@ boolean requiresEagerUnpack() {
202206
}
203207

204208
/**
205-
* Installs truffleattach library. Used reflectively by
206-
* {@code com.oracle.truffle.runtime.JDKSupport}. The {@code JDKSupport} is initialized before
207-
* the Truffle runtime is created and accessor classes are initialized. For this reason, it
208-
* cannot use {@code EngineSupport} to call this method, nor can this method use any accessor.
209+
* Installs {@code truffleattach} library. Used by{@link JDKSupport}. The {@code JDKSupport} is
210+
* initialized before the Truffle runtime is created and accessor classes are initialized. For
211+
* this reason, it cannot use {@code EngineSupport} to call this method, nor can this method use
212+
* any accessor.
209213
*/
210214
static Path installRuntimeResource(InternalResource resource, String id) throws IOException {
211215
InternalResourceCache cache = createRuntimeResourceCache(resource, id);
@@ -219,7 +223,12 @@ static Path installRuntimeResource(InternalResource resource, String id) throws
219223
}
220224
}
221225

222-
private static InternalResourceCache createRuntimeResourceCache(InternalResource resource, String id) {
226+
/**
227+
* Creates an {@link InternalResourceCache} for the {@code truffleattach} library. This method
228+
* is used by {@link JDKSupport} to diagnose the cause of {@code truffleattach} library
229+
* installation failure.
230+
*/
231+
static InternalResourceCache createRuntimeResourceCache(InternalResource resource, String id) {
223232
assert verifyAnnotationConsistency(resource, id) : resource.getClass() + " must be annotated by @InternalResource.Id(\"" + id + "\"";
224233
InternalResourceCache cache = new InternalResourceCache(PolyglotEngineImpl.ENGINE_ID, id, () -> resource);
225234
InternalResourceRoots.initializeRuntimeResource(cache);

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

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.lang.reflect.Method;
5555
import java.net.URI;
5656
import java.nio.ByteBuffer;
57+
import java.nio.file.Files;
5758
import java.nio.file.Path;
5859
import java.util.ArrayDeque;
5960
import java.util.Collections;
@@ -83,6 +84,7 @@
8384
import jdk.internal.access.JavaLangAccess;
8485
import jdk.internal.access.SharedSecrets;
8586
import jdk.internal.module.Modules;
87+
import org.graalvm.collections.Pair;
8688

8789
final class JDKSupport {
8890

@@ -108,36 +110,57 @@ final class JDKSupport {
108110
}
109111
}
110112

111-
private static final ModulesAccessor MODULES_ACCESSOR = initializeModuleAccessor();
113+
/**
114+
* Represents either a {@code ModuleAccessor} or an error message.
115+
* <p>
116+
* The following combinations are valid:
117+
* <ul>
118+
* <li>{@code {moduleAccessor, null}} - a successful result with a module accessor.</li>
119+
* <li>{@code {null, errorMessage}} - a failure with an error message.</li>
120+
* </ul>
121+
*/
122+
private static final Pair<ModulesAccessor, String> MODULES_ACCESSOR = initializeModuleAccessor();
112123

113124
@SuppressWarnings("restricted")
114-
private static ModulesAccessor initializeModuleAccessor() {
125+
private static Pair<ModulesAccessor, String> initializeModuleAccessor() {
115126
String attachLibPath = System.getProperty("truffle.attach.library");
116127
if (attachLibPath == null) {
117128
if (isUnsupportedPlatform()) {
118-
performTruffleAttachLoadFailureAction("Truffle is running on an unsupported platform where the TruffleAttach library is unavailable.", null);
119-
return null;
129+
String errorMessage = "Truffle is running on an unsupported platform where the TruffleAttach library is unavailable.";
130+
performTruffleAttachLoadFailureAction(errorMessage, null);
131+
return Pair.create(null, errorMessage);
120132
}
133+
InternalResource libTruffleAttachResource = new LibTruffleAttachResource();
121134
try {
122-
Path truffleAttachRoot = InternalResourceCache.installRuntimeResource(new LibTruffleAttachResource(), LibTruffleAttachResource.ID);
135+
Path truffleAttachRoot = InternalResourceCache.installRuntimeResource(libTruffleAttachResource, LibTruffleAttachResource.ID);
123136
Path libAttach = truffleAttachRoot.resolve("bin").resolve(System.mapLibraryName("truffleattach"));
124137
attachLibPath = libAttach.toString();
125138
} catch (IOException ioe) {
126-
performTruffleAttachLoadFailureAction("The Truffle API JAR is missing the 'truffleattach' resource, likely due to issues when Truffle was repackaged into a fat JAR.", ioe);
127-
return null;
139+
String errorMessage;
140+
InternalResourceCache cache = InternalResourceCache.createRuntimeResourceCache(libTruffleAttachResource, LibTruffleAttachResource.ID);
141+
Path root = cache.getOwningRoot();
142+
if (root == null || !Files.isDirectory(root) || !Files.isReadable(root) || !Files.isWritable(root)) {
143+
errorMessage = String.format("Failed to install the 'truffleattach' resource: the resource cache folder %s is not a readable and writable directory.", root);
144+
} else {
145+
errorMessage = "The Truffle API JAR is missing the 'truffleattach' resource, likely due to issues when Truffle was repackaged into a fat JAR.";
146+
}
147+
performTruffleAttachLoadFailureAction(errorMessage, ioe);
148+
return Pair.create(null, errorMessage);
128149
}
129150
}
130151
try {
131152
try {
132153
System.load(attachLibPath);
133154
} catch (UnsatisfiedLinkError failedToLoad) {
134-
performTruffleAttachLoadFailureAction("Unable to load the TruffleAttach library.", failedToLoad);
135-
return null;
155+
String errorMessage = String.format("Unable to load the TruffleAttach library %s", attachLibPath);
156+
performTruffleAttachLoadFailureAction(errorMessage, failedToLoad);
157+
return Pair.create(null, errorMessage);
136158
} catch (IllegalCallerException illegalCaller) {
137159
String vmOption = "--enable-native-access=" + (JDKSupport.class.getModule().isNamed() ? "org.graalvm.truffle" : "ALL-UNNAMED");
138-
performTruffleAttachLoadFailureAction(String.format("Failed to load the TruffleAttach library. The Truffle module does not have native access enabled. " +
139-
"To resolve this, pass the following VM option: %s.", vmOption), illegalCaller);
140-
return null;
160+
String errorMessage = String.format("Failed to load the TruffleAttach library. The Truffle module does not have native access enabled. " +
161+
"To resolve this, pass the following VM option: %s.", vmOption);
162+
performTruffleAttachLoadFailureAction(errorMessage, illegalCaller);
163+
return Pair.create(null, errorMessage);
141164
}
142165
ModulesAccessor accessor;
143166
if (ModulesAccessor.class.getModule().isNamed()) {
@@ -148,7 +171,7 @@ private static ModulesAccessor initializeModuleAccessor() {
148171
Module javaBase = ModuleLayer.boot().findModule("java.base").orElseThrow();
149172
addExports0(javaBase, "jdk.internal.module", accessor.getTargetModule());
150173
addExports0(javaBase, "jdk.internal.access", accessor.getTargetModule());
151-
return accessor;
174+
return Pair.create(accessor, null);
152175
} catch (ReflectiveOperationException re) {
153176
throw new InternalError(re);
154177
}
@@ -235,7 +258,11 @@ static void enableNativeAccess(Module clientModule) {
235258

236259
@SuppressWarnings("restricted")
237260
static ModulesAccessor getModulesAccessor() {
238-
return MODULES_ACCESSOR;
261+
return MODULES_ACCESSOR.getLeft();
262+
}
263+
264+
static String getInitializationErrorMessage() {
265+
return MODULES_ACCESSOR.getRight();
239266
}
240267

241268
private static void forEach(Module rootModule, Set<Edge> edges, Predicate<? super Module> filter, Consumer<? super Module> action) {
@@ -699,10 +726,10 @@ private static final class JavaLangSupportImpl extends JavaLangSupport {
699726
static {
700727
if (JDKSupport.MODULES_ACCESSOR == null) {
701728
throw new IllegalStateException("JavaLangAccessorImpl initialized before JDKSupport.");
702-
} else if (!(JDKSupport.MODULES_ACCESSOR instanceof IsolatedImpl)) {
729+
} else if (!(JDKSupport.MODULES_ACCESSOR.getLeft() instanceof IsolatedImpl)) {
703730
throw new IllegalStateException("JDKSupport.MODULES_ACCESSOR initialized with wrong type " + JDKSupport.MODULES_ACCESSOR.getClass());
704731
} else {
705-
CURRENT_CARRIER_THREAD = ((IsolatedImpl) JDKSupport.MODULES_ACCESSOR).currentCarrierThread;
732+
CURRENT_CARRIER_THREAD = ((IsolatedImpl) JDKSupport.MODULES_ACCESSOR.getLeft()).currentCarrierThread;
706733
}
707734
}
708735

truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/ModulesSupport.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ private static String exportJVMCI(Module module, Set<Module> seenModules) {
9595
return "JVMCI is not enabled for this JVM. Enable JVMCI using -XX:+EnableJVMCI.";
9696
}
9797
if (ACCESSOR == null) {
98-
return "The Truffle attach library is not available or cannot be loaded. " +
99-
"This can happen if the Truffle jar files are invalid or if Truffle is loaded multiple times in separate class loaders.";
98+
String initializationError = getAccessorInitializationErrorMessage();
99+
return "The Truffle attach library is not available or cannot be loaded. " + initializationError;
100100
}
101101
addExportsRecursive(layer, jvmciModule, module, seenModules);
102102
return null;
@@ -178,4 +178,18 @@ private static ModulesAccessor initializeModulesAccessor() {
178178
throw new InternalError(throwable);
179179
}
180180
}
181+
182+
/**
183+
* Gets reflectively an error message when {@code ModulesAccessor} failed to initialize.
184+
*/
185+
private static String getAccessorInitializationErrorMessage() {
186+
try {
187+
Class<?> resourceCacheClass = Class.forName("com.oracle.truffle.polyglot.JDKSupport", false, ModulesSupport.class.getClassLoader());
188+
Method getModulesAccessor = resourceCacheClass.getDeclaredMethod("getInitializationErrorMessage");
189+
getModulesAccessor.setAccessible(true);
190+
return (String) getModulesAccessor.invoke(null);
191+
} catch (Throwable throwable) {
192+
return null;
193+
}
194+
}
181195
}

0 commit comments

Comments
 (0)