Skip to content

Commit 3322911

Browse files
committed
[GR-65971] Introduce onSingletonRegistration layered callback.
PullRequest: graal/22049
2 parents 4fd24a6 + a80f10c commit 3322911

File tree

11 files changed

+414
-137
lines changed

11 files changed

+414
-137
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
package com.oracle.svm.core.heap;
2626

2727
import java.util.Collections;
28-
import java.util.EnumSet;
2928
import java.util.List;
3029
import java.util.function.Function;
3130

@@ -37,15 +36,18 @@
3736
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3837
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
3938
import com.oracle.svm.core.feature.InternalFeature;
40-
import com.oracle.svm.core.imagelayer.BuildingImageLayerPredicate;
4139
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
4240
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader;
4341
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter;
4442
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton;
45-
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags;
4643
import com.oracle.svm.core.traits.BuiltinTraits.AllAccess;
4744
import com.oracle.svm.core.traits.BuiltinTraits.SingleLayer;
45+
import com.oracle.svm.core.traits.SingletonLayeredCallbacks;
46+
import com.oracle.svm.core.traits.SingletonLayeredCallbacksSupplier;
47+
import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent;
4848
import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.InitialLayerOnly;
49+
import com.oracle.svm.core.traits.SingletonTrait;
50+
import com.oracle.svm.core.traits.SingletonTraitKind;
4951
import com.oracle.svm.core.traits.SingletonTraits;
5052
import com.oracle.svm.core.util.DuplicatedInNativeCode;
5153
import com.oracle.svm.core.util.ImageHeapList;
@@ -128,7 +130,13 @@ void installGCCause(GCCause gcCause) {
128130
}
129131

130132
@AutomaticallyRegisteredFeature
133+
@SingletonTraits(access = AllAccess.class, layeredCallbacks = GCCauseFeature.LayeredCallbacks.class, layeredInstallationKind = Independent.class)
131134
class GCCauseFeature implements InternalFeature {
135+
/**
136+
* In layered builds all {@link GCCause}s are registered and installed in the initial layer.
137+
* Here we track which {@link GCCause}s were installed in the initial layer to detect issues.
138+
*/
139+
List<String> registeredGCCauses;
132140

133141
@Override
134142
public void duringSetup(DuringSetupAccess access) {
@@ -158,8 +166,7 @@ public void duringSetup(DuringSetupAccess access) {
158166
var gcCauseList = GCCause.getGCCauses();
159167
idToGCCauseName = (idx) -> gcCauseList.get(idx).getName();
160168
} else {
161-
var gcCauseNames = LayeredGCCauseTracker.getRegisteredGCCauses();
162-
idToGCCauseName = gcCauseNames::get;
169+
idToGCCauseName = registeredGCCauses::get;
163170
}
164171
access.registerObjectReplacer(obj -> {
165172
if (obj instanceof GCCause gcCause) {
@@ -172,49 +179,37 @@ public void duringSetup(DuringSetupAccess access) {
172179
});
173180
}
174181
}
175-
}
176-
177-
/**
178-
* In layered builds all {@link GCCause}s are registered and installed in the initial layer. Here we
179-
* track which {@link GCCause}s were installed in the initial layer to detect issues.
180-
*/
181-
@AutomaticallyRegisteredImageSingleton(onlyWith = BuildingImageLayerPredicate.class)
182-
class LayeredGCCauseTracker implements LayeredImageSingleton {
183-
List<String> registeredGCCauses;
184182

185-
public static List<String> getRegisteredGCCauses() {
186-
assert ImageLayerBuildingSupport.buildingExtensionLayer();
187-
return ImageSingletons.lookup(LayeredGCCauseTracker.class).registeredGCCauses;
188-
}
183+
static class LayeredCallbacks extends SingletonLayeredCallbacksSupplier {
184+
185+
@Override
186+
public SingletonTrait getLayeredCallbacksTrait() {
187+
return new SingletonTrait(SingletonTraitKind.LAYERED_CALLBACKS, new SingletonLayeredCallbacks() {
188+
@Override
189+
public LayeredImageSingleton.PersistFlags doPersist(ImageSingletonWriter writer, Object singleton) {
190+
List<String> gcCauses;
191+
if (ImageLayerBuildingSupport.buildingInitialLayer()) {
192+
gcCauses = GCCause.getGCCauses().stream().map(gcCause -> {
193+
if (gcCause == null) {
194+
return "";
195+
} else {
196+
assert !gcCause.getName().isEmpty() : Assertions.errorMessage("Empty string is reserved for non-existent GCCauses", gcCause);
197+
return gcCause.getName();
198+
}
199+
}).toList();
200+
} else {
201+
gcCauses = ((GCCauseFeature) singleton).registeredGCCauses;
202+
}
203+
writer.writeStringList("registeredGCCauses", gcCauses);
189204

190-
@Override
191-
public EnumSet<LayeredImageSingletonBuilderFlags> getImageBuilderFlags() {
192-
return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY;
193-
}
205+
return LayeredImageSingleton.PersistFlags.CALLBACK_ON_REGISTRATION;
206+
}
194207

195-
@Override
196-
public PersistFlags preparePersist(ImageSingletonWriter writer) {
197-
List<String> gcCauses;
198-
if (ImageLayerBuildingSupport.buildingInitialLayer()) {
199-
gcCauses = GCCause.getGCCauses().stream().map(gcCause -> {
200-
if (gcCause == null) {
201-
return "";
202-
} else {
203-
assert !gcCause.getName().isEmpty() : Assertions.errorMessage("Empty string is reserved for non-existent GCCauses", gcCause);
204-
return gcCause.getName();
208+
@Override
209+
public void onSingletonRegistration(ImageSingletonLoader loader, Object singleton) {
210+
((GCCauseFeature) singleton).registeredGCCauses = Collections.unmodifiableList(loader.readStringList("registeredGCCauses"));
205211
}
206-
}).toList();
207-
} else {
208-
gcCauses = registeredGCCauses;
212+
});
209213
}
210-
writer.writeStringList("registeredGCCauses", gcCauses);
211-
return PersistFlags.CREATE;
212-
}
213-
214-
@SuppressWarnings("unused")
215-
public static Object createFromLoader(ImageSingletonLoader loader) {
216-
var causeTracker = new LayeredGCCauseTracker();
217-
causeTracker.registeredGCCauses = Collections.unmodifiableList(loader.readStringList("registeredGCCauses"));
218-
return causeTracker;
219214
}
220215
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.graalvm.nativeimage.Platforms;
3232

3333
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
34+
import com.oracle.svm.core.traits.SingletonLayeredCallbacks;
3435

3536
/**
3637
* In additional to the traditional singleton model, i.e. a key-value map whose lookups are constant
@@ -79,7 +80,18 @@ enum PersistFlags {
7980
* Indicates in a subsequent image a new singleton should be created and linked via calling
8081
* {@code Object createFromLoader(ImageSingletonLoader)}.
8182
*/
82-
CREATE
83+
CREATE,
84+
/**
85+
* Indicates that when and/or if this singleton is registered in the next image via
86+
* {@link ImageSingletons#add}, then
87+
* {@link SingletonLayeredCallbacks#onSingletonRegistration} should be called on this
88+
* singleton.
89+
* <p>
90+
* If a singleton is registered under multiple keys, then
91+
* {@link SingletonLayeredCallbacks#onSingletonRegistration} should only be called once and
92+
* will have finished executing before the singleton is installed into the registry.
93+
*/
94+
CALLBACK_ON_REGISTRATION
8395
}
8496

8597
/*

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/traits/SingletonLayeredCallbacks.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,16 @@ public interface LayeredSingletonInstantiator {
7070
* determine how to instantiate the singleton in the next layer.
7171
*/
7272
public Class<? extends LayeredSingletonInstantiator> getSingletonInstantiator() {
73-
throw VMError.shouldNotReachHere("getSingletonInstantiator is not implemented. This method must only be implemented if doPersist returns PersistFlag.CREATE");
73+
throw VMError.shouldNotReachHere("getSingletonInstantiator is not implemented. This method must be implemented if doPersist returns PersistFlag.CREATE");
74+
}
75+
76+
/**
77+
* See description in {@link PersistFlags#CALLBACK_ON_REGISTRATION} for more details. Note this
78+
* method will be called at most once for each registered singleton object.
79+
*/
80+
@SuppressWarnings("unused")
81+
public void onSingletonRegistration(ImageSingletonLoader loader, Object singleton) {
82+
throw VMError.shouldNotReachHere("onSingletonRegistration is not implemented. This method must be implemented if doPersist returns PersistFlag.CALLBACK_ON_REGISTRATION");
7483
}
7584

7685
}

substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ using MethodId = Int32;
1212
using FieldId = Int32;
1313
using ConstantId = Int32;
1414
using SingletonObjId = Int32;
15+
using KeyStoreId = Int32;
1516
using HostedMethodIndex = Int32;
1617

1718
struct PersistedAnalysisType {
@@ -243,17 +244,23 @@ struct ImageSingletonKey {
243244
objectId @2 :SingletonObjId;
244245
constantId @3 :ConstantId;
245246
isInitialLayerOnly @4 :Bool;
247+
keyStoreId @5 :KeyStoreId;
246248
}
247249

248250
struct ImageSingletonObject {
249251
id @0 :SingletonObjId;
250252
className @1 :Text;
251-
store @2 :List(KeyStoreEntry);
253+
keyStoreId @2 :KeyStoreId;
252254
recreateClass @3 :Text;
253255
# GR-66792 remove once no custom persist actions exist
254256
recreateMethod @4 :Text;
255257
}
256258

259+
struct KeyStoreInstance {
260+
id @0 :KeyStoreId;
261+
keyStore @1 :List(KeyStoreEntry);
262+
}
263+
257264
struct Annotation {
258265
typeName @0 :Text;
259266
values @1 :List(AnnotationValue);
@@ -303,6 +310,7 @@ struct SharedLayerSnapshot {
303310
sharedLayerBootLayerModules @21 :List(Text);
304311
layeredModule @22 :LayeredModule;
305312
cGlobals @23 :List(CGlobalDataInfo);
313+
keyStoreInstances @24 :List(KeyStoreInstance);
306314
}
307315

308316
struct StaticFinalFieldFoldingSingleton {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@
2525
package com.oracle.svm.hosted;
2626

2727
import java.util.Collection;
28+
import java.util.Collections;
2829
import java.util.Comparator;
2930
import java.util.EnumMap;
3031
import java.util.EnumSet;
3132
import java.util.HashSet;
33+
import java.util.IdentityHashMap;
3234
import java.util.Map;
3335
import java.util.Optional;
3436
import java.util.Set;
3537
import java.util.concurrent.ConcurrentHashMap;
38+
import java.util.concurrent.locks.Lock;
39+
import java.util.concurrent.locks.ReentrantLock;
3640
import java.util.function.Function;
3741
import java.util.stream.Collectors;
3842

@@ -355,7 +359,7 @@ public static void clear() {
355359
singletonDuringImageBuild = null;
356360
}
357361

358-
public static void persist() {
362+
public static void persistSingletonInfo() {
359363
var list = singletonDuringImageBuild.configObjects.entrySet().stream().filter(e -> e.getValue().traitMap.getTrait(SingletonTraitKind.LAYERED_CALLBACKS).isPresent())
360364
.sorted(Comparator.comparing(e -> e.getKey().getName()))
361365
.toList();
@@ -364,12 +368,21 @@ public static void persist() {
364368

365369
private final Map<Class<?>, SingletonInfo> configObjects;
366370
private final Map<Object, SingletonTraitMap> singletonToTraitMap;
371+
/**
372+
* Tracks the status of singletons for which a registration callback needs to be executed
373+
* upon installation. The key will always be the singleton object, and the value will be
374+
* either a {@link Boolean} or {@link Lock} based on whether the callback's execution is
375+
* still in progress or has completed.
376+
*/
377+
private final Map<Object, Object> singletonRegistrationCallbackStatus;
367378

368379
private final EnumSet<SingletonLayeredInstallationKind.InstallationKind> forbiddenInstallationKinds;
369380
private Set<Class<?>> multiLayeredImageSingletonKeys;
370381
private final boolean layeredBuild;
382+
private final boolean extensionLayerBuild;
371383
private final AnnotationExtractor extractor;
372384
private final Function<Class<?>, SingletonTrait[]> singletonTraitInjector;
385+
private final SVMImageLayerSingletonLoader singletonLoader;
373386

374387
public HostedManagement() {
375388
this(null, null);
@@ -382,7 +395,10 @@ public HostedManagement(HostedImageLayerBuildingSupport support, AnnotationExtra
382395
forbiddenInstallationKinds = EnumSet.of(SingletonLayeredInstallationKind.InstallationKind.DISALLOWED);
383396
if (support != null) {
384397
this.layeredBuild = support.buildingImageLayer;
398+
this.extensionLayerBuild = support.buildingImageLayer && !support.buildingInitialLayer;
385399
this.singletonTraitInjector = support.getSingletonTraitInjector();
400+
this.singletonLoader = support.getSingletonLoader();
401+
this.singletonRegistrationCallbackStatus = extensionLayerBuild ? new ConcurrentIdentityHashMap<>() : null;
386402
if (support.buildingImageLayer) {
387403
if (!support.buildingApplicationLayer) {
388404
forbiddenInstallationKinds.add(SingletonLayeredInstallationKind.InstallationKind.APP_LAYER_ONLY);
@@ -393,7 +409,10 @@ public HostedManagement(HostedImageLayerBuildingSupport support, AnnotationExtra
393409
}
394410
} else {
395411
this.layeredBuild = false;
412+
this.extensionLayerBuild = false;
396413
this.singletonTraitInjector = null;
414+
this.singletonLoader = null;
415+
this.singletonRegistrationCallbackStatus = null;
397416
}
398417
this.extractor = extractor;
399418
}
@@ -484,12 +503,71 @@ private void addSingletonToMap(Class<?> key, Object value, SingletonTraitMap tra
484503
}
485504
});
486505

506+
/* Run onSingletonRegistration hook if needed. */
507+
if (extensionLayerBuild) {
508+
if (singletonLoader.hasRegistrationCallback(key)) {
509+
synchronizeRegistrationCallbackExecution(value, () -> {
510+
var trait = traitMap.getTrait(SingletonTraitKind.LAYERED_CALLBACKS).get();
511+
var callbacks = ((SingletonLayeredCallbacks) trait.metadata());
512+
callbacks.onSingletonRegistration(singletonLoader.getImageSingletonLoader(key), value);
513+
});
514+
}
515+
}
516+
487517
Object prevValue = configObjects.putIfAbsent(key, new SingletonInfo(value, traitMap));
488518
if (prevValue != null) {
489519
throw UserError.abort("ImageSingletons.add must not overwrite existing key %s%nExisting value: %s%nNew value: %s", key.getTypeName(), prevValue, value);
490520
}
491521
}
492522

523+
/**
524+
* Ensures the provided registrationCallback will execute only once per a singleton.
525+
* Regardless of which thread executes the registrationCallback, this method will not return
526+
* until the registrationCallback has been executed.
527+
*/
528+
private void synchronizeRegistrationCallbackExecution(Object singleton, Runnable registrationCallback) {
529+
while (true) {
530+
var status = singletonRegistrationCallbackStatus.get(singleton);
531+
if (status == null) {
532+
// create a lock for other threads to wait on
533+
ReentrantLock lock = new ReentrantLock();
534+
lock.lock();
535+
try {
536+
status = singletonRegistrationCallbackStatus.computeIfAbsent(singleton, _ -> lock);
537+
if (status != lock) {
538+
// failed to install lock. Repeat loop.
539+
continue;
540+
}
541+
542+
// Run registrationCallback
543+
registrationCallback.run();
544+
545+
// the registrationCallback has finished - update its status
546+
var prev = singletonRegistrationCallbackStatus.put(singleton, Boolean.TRUE);
547+
VMError.guarantee(prev == lock);
548+
} finally {
549+
lock.unlock();
550+
}
551+
} else if (status instanceof Lock lock) {
552+
lock.lock();
553+
try {
554+
/*
555+
* Once the lock can be acquired we know the registrationCallback has been
556+
* completed and we can proceed.
557+
*/
558+
assert singletonRegistrationCallbackStatus.get(singleton) == Boolean.TRUE;
559+
} finally {
560+
lock.unlock();
561+
}
562+
} else {
563+
// the registrationCallback has already completed
564+
assert status == Boolean.TRUE;
565+
}
566+
/* At this point the registrationCallback has executed so it is safe to proceed. */
567+
break;
568+
}
569+
}
570+
493571
Collection<Class<?>> getMultiLayeredImageSingletonKeys() {
494572
return multiLayeredImageSingletonKeys;
495573
}
@@ -499,7 +577,7 @@ void freezeLayeredImageSingletonMetadata() {
499577
}
500578

501579
Set<Object> getSingletonsWithTrait(SingletonLayeredInstallationKind.InstallationKind kind) {
502-
return configObjects.values().stream().filter(singletonInfo -> {
580+
return Collections.unmodifiableSet(configObjects.values().stream().filter(singletonInfo -> {
503581
/*
504582
* We must filter out forbidden objects, as they are not actually installed in this
505583
* image.
@@ -511,7 +589,7 @@ Set<Object> getSingletonsWithTrait(SingletonLayeredInstallationKind.Installation
511589
}
512590
}
513591
return false;
514-
}).map(SingletonInfo::singleton).collect(Collectors.toUnmodifiableSet());
592+
}).map(SingletonInfo::singleton).collect(Collectors.toCollection(() -> Collections.newSetFromMap(new IdentityHashMap<>()))));
515593
}
516594

517595
void forbidNewTraitInstallations(SingletonLayeredInstallationKind.InstallationKind kind) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ protected void doRun(Map<Method, CEntryPointData> entryPoints, JavaMainSupport j
757757
}
758758
try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.ARCHIVE_LAYER)) {
759759
if (ImageLayerBuildingSupport.buildingSharedLayer()) {
760-
ImageSingletonsSupportImpl.HostedManagement.persist();
760+
ImageSingletonsSupportImpl.HostedManagement.persistSingletonInfo();
761761
HostedImageLayerBuildingSupport.singleton().archiveLayer();
762762
}
763763
}

0 commit comments

Comments
 (0)