Skip to content

Commit

Permalink
Refine native agent detection
Browse files Browse the repository at this point in the history
This commit refines how native agent detection works
for both test and application executions.

It rolls back the introduction of TestAotDetector done in 1113096
and instead updates AotDetector.useGeneratedArtifacts()
to only detect "buildtime" and "runtime" imagecode system
property values by leveraging a new method
NativeDetector.inNativeImage(NativeDetector.Context...).

This commit also adds a workaround for
oracle/graal#6691.

Closes spring-projectsgh-30511
  • Loading branch information
sdeleuze committed May 26, 2023
1 parent 8b8d147 commit bef08ec
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,11 +19,14 @@
import org.springframework.core.NativeDetector;
import org.springframework.core.SpringProperties;

import static org.springframework.core.NativeDetector.Context;

/**
* Utility for determining if AOT-processed optimizations must be used rather
* than the regular runtime. Strictly for internal use within the framework.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
*/
public abstract class AotDetector {
Expand All @@ -36,14 +39,16 @@ public abstract class AotDetector {
*/
public static final String AOT_ENABLED = "spring.aot.enabled";

private static final boolean inNativeImage = NativeDetector.inNativeImage(Context.RUNTIME, Context.BUILD_TIME);

/**
* Determine whether AOT optimizations must be considered at runtime. This
* is mandatory in a native image but can be triggered on the JVM using
* the {@value #AOT_ENABLED} Spring property.
* @return whether AOT optimizations must be considered
*/
public static boolean useGeneratedArtifacts() {
return (NativeDetector.inNativeImage() || SpringProperties.getFlag(AOT_ENABLED));
return (inNativeImage || SpringProperties.getFlag(AOT_ENABLED));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
class PreComputeFieldFeature implements Feature {

private static Pattern[] patterns = {
Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#imageCode")),
Pattern.compile(Pattern.quote("org.springframework.cglib.core.AbstractClassGenerator#imageCode")),
Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#inNativeImage")),
Pattern.compile(Pattern.quote("org.springframework.cglib.core.AbstractClassGenerator#inNativeImage")),
Pattern.compile(Pattern.quote("org.springframework.aot.AotDetector#inNativeImage")),
Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*Present"),
Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*PRESENT"),
Pattern.compile(Pattern.quote("reactor.") + ".*#.*Available"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class AbstractClassGenerator<T> implements ClassGenerator {

static {
String imageCode = System.getProperty("org.graalvm.nativeimage.imagecode");
inNativeImage = "buildtime".equals(imageCode) || "runtime".equals(imageCode);
}

private static final ThreadLocal CURRENT = new ThreadLocal();

private static volatile Map<ClassLoader, ClassLoaderData> CACHE = new WeakHashMap<>();

private static final boolean DEFAULT_USE_CACHE =
Boolean.parseBoolean(System.getProperty("cglib.useCache", "true"));

// See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java
private static final boolean imageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
private static final boolean inNativeImage;


private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE;
Expand Down Expand Up @@ -354,7 +358,7 @@ protected Class generate(ClassLoaderData data) {
}
}
// SPRING PATCH BEGIN
if (imageCode) {
if (inNativeImage) {
throw new UnsupportedOperationException("CGLIB runtime enhancement not supported on native image. " +
"Make sure to include a pre-generated class on the classpath instead: " + getClassName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.core;

import org.springframework.lang.Nullable;

/**
* A common delegate for detecting a GraalVM native image environment.
*
Expand All @@ -25,12 +27,60 @@
public abstract class NativeDetector {

// See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java
private static final boolean imageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
@Nullable
private static final String imageCode = System.getProperty("org.graalvm.nativeimage.imagecode");

private static final boolean inNativeImage = (imageCode != null);

/**
* Returns {@code true} if invoked in the context of image building or during image runtime, else {@code false}.
* Returns {@code true} if running in the context of a native image flavor (build-time, runtime, agent) expressed
* by setting {@code org.graalvm.nativeimage.imagecode} system property to any value, else {@code false}.
*/
public static boolean inNativeImage() {
return imageCode;
return inNativeImage;
}

/**
* Returns {@code true} if running in the context of a native image flavor (build-time, runtime, agent) expressed
* by setting {@code org.graalvm.nativeimage.imagecode} system property, else {@code false}.
* @since 6.0.10
*/
public static boolean inNativeImage(Context... contexts) {
for (Context context: contexts) {
if (context.key.equals(imageCode)) {
return true;
}
}
return false;
}

/**
* Native context as defined in
* <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>.
*
* @since 6.0.10
*/
public enum Context {

/**
* The code is executing in the context of image building.
*/
BUILD_TIME("buildtime"),

/**
* The code is executing at image runtime.
*/
RUNTIME("runtime");

private final String key;

Context(final String key) {
this.key = key;
}

@Override
public String toString() {
return this.key;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.test.context.aot;

import org.springframework.aot.AotDetector;
import org.springframework.lang.Nullable;

/**
Expand All @@ -26,7 +27,7 @@
* and run-time. At build time, test components can {@linkplain #setAttribute contribute}
* attributes during the AOT processing phase. At run time, test components can
* {@linkplain #getString(String) retrieve} attributes that were contributed at
* build time. If {@link TestAotDetector#useGeneratedArtifacts()} returns {@code true},
* build time. If {@link AotDetector#useGeneratedArtifacts()} returns {@code true},
* run-time mode applies.
*
* <p>For example, if a test component computes something at build time that
Expand All @@ -43,7 +44,7 @@
* &mdash; can choose to contribute an attribute at any point in time. Note that
* contributing an attribute during standard JVM test execution will not have any
* adverse side effect since AOT attributes will be ignored in that scenario. In
* any case, you should use {@link TestAotDetector#useGeneratedArtifacts()} to determine
* any case, you should use {@link AotDetector#useGeneratedArtifacts()} to determine
* if invocations of {@link #setAttribute(String, String)} and
* {@link #removeAttribute(String)} are permitted.
*
Expand All @@ -70,12 +71,12 @@ static AotTestAttributes getInstance() {
* @param name the unique attribute name
* @param value the associated attribute value
* @throws UnsupportedOperationException if invoked during
* {@linkplain TestAotDetector#useGeneratedArtifacts() AOT run-time execution}
* {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
* @throws IllegalArgumentException if the provided value is {@code null} or
* if an attempt is made to override an existing attribute
* @see #setAttribute(String, boolean)
* @see #removeAttribute(String)
* @see TestAotDetector#useGeneratedArtifacts()
* @see AotDetector#useGeneratedArtifacts()
*/
void setAttribute(String name, String value);

Expand All @@ -87,13 +88,13 @@ static AotTestAttributes getInstance() {
* @param name the unique attribute name
* @param value the associated attribute value
* @throws UnsupportedOperationException if invoked during
* {@linkplain TestAotDetector#useGeneratedArtifacts() AOT run-time execution}
* {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
* @throws IllegalArgumentException if an attempt is made to override an
* existing attribute
* @see #setAttribute(String, String)
* @see #removeAttribute(String)
* @see Boolean#toString(boolean)
* @see TestAotDetector#useGeneratedArtifacts()
* @see AotDetector#useGeneratedArtifacts()
*/
default void setAttribute(String name, boolean value) {
setAttribute(name, Boolean.toString(value));
Expand All @@ -103,8 +104,8 @@ default void setAttribute(String name, boolean value) {
* Remove the attribute stored under the provided name.
* @param name the unique attribute name
* @throws UnsupportedOperationException if invoked during
* {@linkplain TestAotDetector#useGeneratedArtifacts() AOT run-time execution}
* @see TestAotDetector#useGeneratedArtifacts()
* {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
* @see AotDetector#useGeneratedArtifacts()
* @see #setAttribute(String, String)
*/
void removeAttribute(String name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.aot.AotDetector;
import org.springframework.lang.Nullable;

/**
Expand All @@ -39,7 +40,7 @@ private AotTestAttributesFactory() {
/**
* Get the underlying attributes map.
* <p>If the map is not already loaded, this method loads the map from the
* generated class when running in {@linkplain TestAotDetector#useGeneratedArtifacts()
* generated class when running in {@linkplain AotDetector#useGeneratedArtifacts()
* AOT execution mode} and otherwise creates a new map for storing attributes
* during the AOT processing phase.
*/
Expand All @@ -49,7 +50,7 @@ static Map<String, String> getAttributes() {
synchronized (AotTestAttributesFactory.class) {
attrs = attributes;
if (attrs == null) {
attrs = (TestAotDetector.useGeneratedArtifacts() ? loadAttributesMap() : new ConcurrentHashMap<>());
attrs = (AotDetector.useGeneratedArtifacts() ? loadAttributesMap() : new ConcurrentHashMap<>());
attributes = attrs;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Map;
import java.util.function.Supplier;

import org.springframework.aot.AotDetector;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.Nullable;
Expand All @@ -29,7 +30,7 @@
*
* <p>Intended solely for internal use within the framework.
*
* <p>If we are not running in {@linkplain TestAotDetector#useGeneratedArtifacts()
* <p>If we are not running in {@linkplain AotDetector#useGeneratedArtifacts()
* AOT mode} or if a test class is not {@linkplain #isSupportedTestClass(Class)
* supported} in AOT mode, {@link #getContextInitializer(Class)} and
* {@link #getContextInitializerClass(Class)} will return {@code null}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Map;
import java.util.function.Supplier;

import org.springframework.aot.AotDetector;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.Nullable;
Expand All @@ -44,7 +45,7 @@ private AotTestContextInitializersFactory() {
/**
* Get the underlying map.
* <p>If the map is not already loaded, this method loads the map from the
* generated class when running in {@linkplain TestAotDetector#useGeneratedArtifacts()
* generated class when running in {@linkplain AotDetector#useGeneratedArtifacts()
* AOT execution mode} and otherwise creates an immutable, empty map.
*/
static Map<String, Supplier<ApplicationContextInitializer<ConfigurableApplicationContext>>> getContextInitializers() {
Expand All @@ -53,7 +54,7 @@ static Map<String, Supplier<ApplicationContextInitializer<ConfigurableApplicatio
synchronized (AotTestContextInitializersFactory.class) {
initializers = contextInitializers;
if (initializers == null) {
initializers = (TestAotDetector.useGeneratedArtifacts() ? loadContextInitializersMap() : Map.of());
initializers = (AotDetector.useGeneratedArtifacts() ? loadContextInitializersMap() : Map.of());
contextInitializers = initializers;
}
}
Expand All @@ -67,7 +68,7 @@ static Map<String, Class<ApplicationContextInitializer<?>>> getContextInitialize
synchronized (AotTestContextInitializersFactory.class) {
initializerClasses = contextInitializerClasses;
if (initializerClasses == null) {
initializerClasses = (TestAotDetector.useGeneratedArtifacts() ? loadContextInitializerClassesMap() : Map.of());
initializerClasses = (AotDetector.useGeneratedArtifacts() ? loadContextInitializerClassesMap() : Map.of());
contextInitializerClasses = initializerClasses;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Map;

import org.springframework.aot.AotDetector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Expand Down Expand Up @@ -60,7 +61,7 @@ public String getString(String name) {


private static void assertNotInAotRuntime() {
if (TestAotDetector.useGeneratedArtifacts()) {
if (AotDetector.useGeneratedArtifacts()) {
throw new UnsupportedOperationException(
"AOT attributes cannot be modified during AOT run-time execution");
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.aot.AotDetector;
import org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.GeneratedClasses;
Expand Down Expand Up @@ -122,7 +123,7 @@ public final RuntimeHints getRuntimeHints() {
* @throws TestContextAotException if an error occurs during AOT processing
*/
public void processAheadOfTime(Stream<Class<?>> testClasses) throws TestContextAotException {
Assert.state(!TestAotDetector.useGeneratedArtifacts(), "Cannot perform AOT processing during AOT run-time execution");
Assert.state(!AotDetector.useGeneratedArtifacts(), "Cannot perform AOT processing during AOT run-time execution");
try {
resetAotFactories();

Expand Down

0 comments on commit bef08ec

Please sign in to comment.