2525package com .oracle .svm .hosted ;
2626
2727import java .util .Collection ;
28+ import java .util .Collections ;
2829import java .util .Comparator ;
2930import java .util .EnumMap ;
3031import java .util .EnumSet ;
3132import java .util .HashSet ;
33+ import java .util .IdentityHashMap ;
3234import java .util .Map ;
3335import java .util .Optional ;
3436import java .util .Set ;
3537import java .util .concurrent .ConcurrentHashMap ;
38+ import java .util .concurrent .locks .Lock ;
39+ import java .util .concurrent .locks .ReentrantLock ;
3640import java .util .function .Function ;
3741import 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 ) {
0 commit comments