25
25
package com .oracle .svm .hosted ;
26
26
27
27
import java .util .Collection ;
28
+ import java .util .Collections ;
28
29
import java .util .Comparator ;
29
30
import java .util .EnumMap ;
30
31
import java .util .EnumSet ;
31
32
import java .util .HashSet ;
33
+ import java .util .IdentityHashMap ;
32
34
import java .util .Map ;
33
35
import java .util .Optional ;
34
36
import java .util .Set ;
35
37
import java .util .concurrent .ConcurrentHashMap ;
38
+ import java .util .concurrent .locks .Lock ;
39
+ import java .util .concurrent .locks .ReentrantLock ;
36
40
import java .util .function .Function ;
37
41
import java .util .stream .Collectors ;
38
42
@@ -355,7 +359,7 @@ public static void clear() {
355
359
singletonDuringImageBuild = null ;
356
360
}
357
361
358
- public static void persist () {
362
+ public static void persistSingletonInfo () {
359
363
var list = singletonDuringImageBuild .configObjects .entrySet ().stream ().filter (e -> e .getValue ().traitMap .getTrait (SingletonTraitKind .LAYERED_CALLBACKS ).isPresent ())
360
364
.sorted (Comparator .comparing (e -> e .getKey ().getName ()))
361
365
.toList ();
@@ -364,12 +368,21 @@ public static void persist() {
364
368
365
369
private final Map <Class <?>, SingletonInfo > configObjects ;
366
370
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 ;
367
378
368
379
private final EnumSet <SingletonLayeredInstallationKind .InstallationKind > forbiddenInstallationKinds ;
369
380
private Set <Class <?>> multiLayeredImageSingletonKeys ;
370
381
private final boolean layeredBuild ;
382
+ private final boolean extensionLayerBuild ;
371
383
private final AnnotationExtractor extractor ;
372
384
private final Function <Class <?>, SingletonTrait []> singletonTraitInjector ;
385
+ private final SVMImageLayerSingletonLoader singletonLoader ;
373
386
374
387
public HostedManagement () {
375
388
this (null , null );
@@ -382,7 +395,10 @@ public HostedManagement(HostedImageLayerBuildingSupport support, AnnotationExtra
382
395
forbiddenInstallationKinds = EnumSet .of (SingletonLayeredInstallationKind .InstallationKind .DISALLOWED );
383
396
if (support != null ) {
384
397
this .layeredBuild = support .buildingImageLayer ;
398
+ this .extensionLayerBuild = support .buildingImageLayer && !support .buildingInitialLayer ;
385
399
this .singletonTraitInjector = support .getSingletonTraitInjector ();
400
+ this .singletonLoader = support .getSingletonLoader ();
401
+ this .singletonRegistrationCallbackStatus = extensionLayerBuild ? new ConcurrentIdentityHashMap <>() : null ;
386
402
if (support .buildingImageLayer ) {
387
403
if (!support .buildingApplicationLayer ) {
388
404
forbiddenInstallationKinds .add (SingletonLayeredInstallationKind .InstallationKind .APP_LAYER_ONLY );
@@ -393,7 +409,10 @@ public HostedManagement(HostedImageLayerBuildingSupport support, AnnotationExtra
393
409
}
394
410
} else {
395
411
this .layeredBuild = false ;
412
+ this .extensionLayerBuild = false ;
396
413
this .singletonTraitInjector = null ;
414
+ this .singletonLoader = null ;
415
+ this .singletonRegistrationCallbackStatus = null ;
397
416
}
398
417
this .extractor = extractor ;
399
418
}
@@ -484,12 +503,71 @@ private void addSingletonToMap(Class<?> key, Object value, SingletonTraitMap tra
484
503
}
485
504
});
486
505
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
+
487
517
Object prevValue = configObjects .putIfAbsent (key , new SingletonInfo (value , traitMap ));
488
518
if (prevValue != null ) {
489
519
throw UserError .abort ("ImageSingletons.add must not overwrite existing key %s%nExisting value: %s%nNew value: %s" , key .getTypeName (), prevValue , value );
490
520
}
491
521
}
492
522
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
+
493
571
Collection <Class <?>> getMultiLayeredImageSingletonKeys () {
494
572
return multiLayeredImageSingletonKeys ;
495
573
}
@@ -499,7 +577,7 @@ void freezeLayeredImageSingletonMetadata() {
499
577
}
500
578
501
579
Set <Object > getSingletonsWithTrait (SingletonLayeredInstallationKind .InstallationKind kind ) {
502
- return configObjects .values ().stream ().filter (singletonInfo -> {
580
+ return Collections . unmodifiableSet ( configObjects .values ().stream ().filter (singletonInfo -> {
503
581
/*
504
582
* We must filter out forbidden objects, as they are not actually installed in this
505
583
* image.
@@ -511,7 +589,7 @@ Set<Object> getSingletonsWithTrait(SingletonLayeredInstallationKind.Installation
511
589
}
512
590
}
513
591
return false ;
514
- }).map (SingletonInfo ::singleton ).collect (Collectors .toUnmodifiableSet ( ));
592
+ }).map (SingletonInfo ::singleton ).collect (Collectors .toCollection (() -> Collections . newSetFromMap ( new IdentityHashMap <>())) ));
515
593
}
516
594
517
595
void forbidNewTraitInstallations (SingletonLayeredInstallationKind .InstallationKind kind ) {
0 commit comments