Skip to content

Commit 9c8a28f

Browse files
committed
[GR-65847] De-duplicate resources in extension layers
PullRequest: graal/21099
2 parents 052977f + 18636ac commit 9c8a28f

File tree

1 file changed

+129
-9
lines changed
  • substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk

1 file changed

+129
-9
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@
3535
import java.util.Date;
3636
import java.util.EnumSet;
3737
import java.util.Enumeration;
38+
import java.util.HashMap;
39+
import java.util.HashSet;
3840
import java.util.List;
41+
import java.util.Map;
3942
import java.util.Objects;
4043
import java.util.Set;
4144
import java.util.function.BiConsumer;
4245
import java.util.function.Function;
4346
import java.util.stream.Collectors;
4447
import java.util.stream.StreamSupport;
4548

46-
import com.oracle.svm.core.encoder.SymbolEncoder;
4749
import org.graalvm.collections.EconomicMap;
4850
import org.graalvm.collections.MapCursor;
4951
import org.graalvm.nativeimage.ImageInfo;
@@ -59,8 +61,10 @@
5961
import com.oracle.svm.core.SubstrateUtil;
6062
import com.oracle.svm.core.configure.ConditionalRuntimeValue;
6163
import com.oracle.svm.core.configure.RuntimeConditionSet;
64+
import com.oracle.svm.core.encoder.SymbolEncoder;
6265
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
6366
import com.oracle.svm.core.feature.InternalFeature;
67+
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
6468
import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError;
6569
import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils;
6670
import com.oracle.svm.core.jdk.resources.ResourceExceptionEntry;
@@ -69,10 +73,11 @@
6973
import com.oracle.svm.core.jdk.resources.ResourceURLConnection;
7074
import com.oracle.svm.core.jdk.resources.CompressedGlobTrie.CompressedGlobTrie;
7175
import com.oracle.svm.core.jdk.resources.CompressedGlobTrie.GlobTrieNode;
76+
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader;
77+
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter;
7278
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags;
7379
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport;
7480
import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton;
75-
import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton;
7681
import com.oracle.svm.core.metadata.MetadataTracer;
7782
import com.oracle.svm.core.util.ImageHeapMap;
7883
import com.oracle.svm.core.util.VMError;
@@ -87,11 +92,16 @@
8792
* Registered resources are then available from DynamicHub#getResource classes and
8893
* {@link Target_java_lang_ClassLoader class loaders}.
8994
*/
90-
public final class Resources implements MultiLayeredImageSingleton, UnsavedSingleton {
95+
public final class Resources implements MultiLayeredImageSingleton {
9196

9297
private static final int INVALID_TIMESTAMP = -1;
9398
public static final char RESOURCES_INTERNAL_PATH_SEPARATOR = '/';
94-
private final SymbolEncoder encoder = SymbolEncoder.singleton();
99+
private static final String RESOURCE_KEYS = "resourceKeys";
100+
private static final String RESOURCE_REGISTRATION_STATES = "resourceRegistrationStates";
101+
private static final String PATTERNS = "patterns";
102+
103+
@Platforms(Platform.HOSTED_ONLY.class) //
104+
private SymbolEncoder encoder;
95105

96106
/**
97107
* @return the singleton corresponding to this layer's resources in a layered build, the unique
@@ -121,6 +131,27 @@ public static Resources[] layeredSingletons() {
121131
private final EconomicMap<ModuleResourceKey, ConditionalRuntimeValue<ResourceStorageEntryBase>> resources = ImageHeapMap.createNonLayeredMap();
122132
private final EconomicMap<RequestedPattern, RuntimeConditionSet> requestedPatterns = ImageHeapMap.createNonLayeredMap();
123133

134+
/**
135+
* The string representation of {@link ModuleResourceKey} that are already registered in
136+
* previous layers. Since the {@link ModuleResourceKey} contains a reference to a
137+
* {@link Module}, the {@link Module} name is used instead of the object itself in the string
138+
* representation. This works under the assumption that all modules have a different unique name
139+
* in Layered Images. More details can be found in
140+
* {@link Resources#getModuleResourceKeyString(ModuleResourceKey)}.
141+
*
142+
* The boolean associated to each {@link ModuleResourceKey} is true if the registered value is
143+
* complete and false in the case of a negative query.
144+
*/
145+
@Platforms(Platform.HOSTED_ONLY.class) //
146+
private final Map<String, Boolean> previousLayerResources;
147+
148+
/**
149+
* The string representation of {@link RequestedPattern} that are already registered in previous
150+
* layers.
151+
*/
152+
@Platforms(Platform.HOSTED_ONLY.class) //
153+
private final Set<String> previousLayerPatterns;
154+
124155
public record RequestedPattern(String module, String resource) {
125156
}
126157

@@ -155,6 +186,17 @@ public record ModuleResourceKey(Module module, String resource) {
155186
private Function<Module, Module> hostedToRuntimeModuleMapper;
156187

157188
Resources() {
189+
this(Map.of(), Set.of());
190+
}
191+
192+
Resources(Map<String, Boolean> previousLayerResources, Set<String> previousLayerPatterns) {
193+
this.previousLayerResources = previousLayerResources;
194+
this.previousLayerPatterns = previousLayerPatterns;
195+
}
196+
197+
@Platforms(Platform.HOSTED_ONLY.class)
198+
public void setEncoder(SymbolEncoder encoder) {
199+
this.encoder = encoder;
158200
}
159201

160202
public GlobTrieNode<ConditionWithOrigin> getResourcesTrieRoot() {
@@ -240,6 +282,26 @@ private void updateTimeStamp() {
240282
}
241283
}
242284

285+
private static String getModuleResourceKeyString(ModuleResourceKey m) {
286+
/*
287+
* A null module in the ModuleResourceKey represents any unnamed module, meaning that only
288+
* one marker is needed for all of them and that if the module is not null, it is named (see
289+
* Resources.createStorageKey). This string representation relies on the assumption that a
290+
* layered image build cannot contain two modules with the same name, so Module#getName() is
291+
* guaranteed to be unique for layered images.
292+
*/
293+
String moduleName = m.module == null ? LayeredModuleSingleton.ALL_UNNAMED_MODULE_NAME : m.module.getName();
294+
return moduleName + m.resource;
295+
}
296+
297+
private void addResource(ModuleResourceKey key, ConditionalRuntimeValue<ResourceStorageEntryBase> entry) {
298+
String moduleResourceKeyString = getModuleResourceKeyString(key);
299+
Boolean previousLayerData = previousLayerResources.get(moduleResourceKeyString);
300+
if (previousLayerData == null || (!previousLayerData && entry.getValueUnconditionally() != NEGATIVE_QUERY_MARKER)) {
301+
resources.put(key, entry);
302+
}
303+
}
304+
243305
@Platforms(Platform.HOSTED_ONLY.class)
244306
private void addEntry(Module module, String resourceName, boolean isDirectory, byte[] data, boolean fromJar, boolean isNegativeQuery) {
245307
VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Trying to add a resource entry after analysis.");
@@ -250,15 +312,15 @@ private void addEntry(Module module, String resourceName, boolean isDirectory, b
250312
ConditionalRuntimeValue<ResourceStorageEntryBase> entry = resources.get(key);
251313
if (isNegativeQuery) {
252314
if (entry == null) {
253-
resources.put(key, new ConditionalRuntimeValue<>(conditionSet, NEGATIVE_QUERY_MARKER));
315+
addResource(key, new ConditionalRuntimeValue<>(conditionSet, NEGATIVE_QUERY_MARKER));
254316
}
255317
return;
256318
}
257319

258320
if (entry == null || entry.getValueUnconditionally() == NEGATIVE_QUERY_MARKER) {
259321
updateTimeStamp();
260322
entry = new ConditionalRuntimeValue<>(conditionSet, new ResourceStorageEntry(isDirectory, fromJar));
261-
resources.put(key, entry);
323+
addResource(key, entry);
262324
} else {
263325
if (key.module() != null) {
264326
// if the entry already exists, and it comes from a module, it is the same entry
@@ -307,7 +369,7 @@ public void registerIOException(Module module, String resourceName, IOException
307369
ModuleResourceKey key = createStorageKey(module, resourceName);
308370
synchronized (resources) {
309371
updateTimeStamp();
310-
resources.put(key, new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), new ResourceExceptionEntry(e)));
372+
addResource(key, new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), new ResourceExceptionEntry(e)));
311373
}
312374
}
313375

@@ -326,7 +388,13 @@ public void registerIncludePattern(ConfigurationCondition condition, String modu
326388
assert MissingRegistrationUtils.throwMissingRegistrationErrors();
327389
synchronized (requestedPatterns) {
328390
updateTimeStamp();
329-
requestedPatterns.put(new RequestedPattern(encoder.encodeModule(module), handleEscapedCharacters(pattern)), RuntimeConditionSet.createHosted(condition));
391+
addPattern(new RequestedPattern(encoder.encodeModule(module), handleEscapedCharacters(pattern)), RuntimeConditionSet.createHosted(condition));
392+
}
393+
}
394+
395+
private void addPattern(RequestedPattern pattern, RuntimeConditionSet condition) {
396+
if (!previousLayerPatterns.contains(pattern.toString())) {
397+
requestedPatterns.put(pattern, condition);
330398
}
331399
}
332400

@@ -601,13 +669,65 @@ private static boolean matchResource(String pattern, String resource) {
601669
public EnumSet<LayeredImageSingletonBuilderFlags> getImageBuilderFlags() {
602670
return LayeredImageSingletonBuilderFlags.ALL_ACCESS;
603671
}
672+
673+
@Override
674+
public PersistFlags preparePersist(ImageSingletonWriter writer) {
675+
List<String> resourceKeys = new ArrayList<>();
676+
List<Boolean> resourceRegistrationStates = new ArrayList<>();
677+
Set<String> patterns = new HashSet<>(previousLayerPatterns);
678+
679+
var cursor = resources.getEntries();
680+
while (cursor.advance()) {
681+
resourceKeys.add(getModuleResourceKeyString(cursor.getKey()));
682+
boolean isNegativeQuery = cursor.getValue().getValueUnconditionally() == NEGATIVE_QUERY_MARKER;
683+
resourceRegistrationStates.add(!isNegativeQuery);
684+
}
685+
686+
for (var entry : previousLayerResources.entrySet()) {
687+
/*
688+
* If a complete entry overwrites a negative query from a previous layer, the
689+
* previousLayerResources map entry needs to be skipped to register the new entry for
690+
* extension layers.
691+
*/
692+
if (!resourceKeys.contains(entry.getKey())) {
693+
resourceKeys.add(entry.getKey());
694+
resourceRegistrationStates.add(entry.getValue());
695+
}
696+
}
697+
698+
requestedPatterns.getKeys().forEach(p -> patterns.add(p.toString()));
699+
700+
writer.writeStringList(RESOURCE_KEYS, resourceKeys);
701+
writer.writeBoolList(RESOURCE_REGISTRATION_STATES, resourceRegistrationStates);
702+
writer.writeStringList(PATTERNS, patterns.stream().toList());
703+
704+
return PersistFlags.CREATE;
705+
}
706+
707+
@SuppressWarnings("unused")
708+
public static Object createFromLoader(ImageSingletonLoader loader) {
709+
List<String> previousLayerResourceKeys = loader.readStringList(RESOURCE_KEYS);
710+
List<Boolean> previousLayerRegistrationStates = loader.readBoolList(RESOURCE_REGISTRATION_STATES);
711+
Map<String, Boolean> previousLayerResources = new HashMap<>();
712+
713+
for (int i = 0; i < previousLayerResourceKeys.size(); ++i) {
714+
previousLayerResources.put(previousLayerResourceKeys.get(i), previousLayerRegistrationStates.get(i));
715+
}
716+
717+
Set<String> previousLayerPatterns = Set.copyOf(loader.readStringList(PATTERNS));
718+
719+
return new Resources(Collections.unmodifiableMap(previousLayerResources), previousLayerPatterns);
720+
}
604721
}
605722

606723
@AutomaticallyRegisteredFeature
607724
final class ResourcesFeature implements InternalFeature {
608725
@Override
609726
public void afterRegistration(AfterRegistrationAccess access) {
610-
ImageSingletons.add(Resources.class, new Resources());
727+
if (ImageLayerBuildingSupport.firstImageBuild()) {
728+
ImageSingletons.add(Resources.class, new Resources());
729+
}
730+
Resources.currentLayer().setEncoder(SymbolEncoder.singleton());
611731
}
612732

613733
@Override

0 commit comments

Comments
 (0)