From e75f9cc1e4553b78d4c893b2f631f11e326ec82f Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 8 Dec 2025 22:11:54 +0100 Subject: [PATCH 1/5] fix: fixes #2413 so that startup order of APIs stays the same - Added `start()` method to `BeanRegistry` interface and its implementation for lifecycle management. - Documented thread-safety expectations and constraints for `registerBeanDefinitions()` and `start()`. - Updated usage in relevant classes to adhere to the modified lifecycle flow. --- .../membrane/annot/yaml/BeanRegistry.java | 56 ++++++++++++++++- .../yaml/BeanRegistryImplementation.java | 63 +++++++++++++++++-- .../membrane/annot/util/YamlParser.java | 1 + .../predic8/membrane/core/cli/RouterCLI.java | 4 +- .../kubernetes/GenericYamlParserTest.java | 5 ++ 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java index 01f56ed6b6..b0bce7a341 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java @@ -15,7 +15,7 @@ import com.predic8.membrane.annot.*; -import java.util.List; +import java.util.*; public interface BeanRegistry { @@ -23,8 +23,62 @@ public interface BeanRegistry { List getBeans(); + /** + * Registers a set of {@link BeanDefinition}s originating from static configuration + * (for example YAML files on startup). + * + *

Thread–safety model:

+ * + * + *

Lifecycle expectations:

+ * + * + * @param beanDefinitions the list of static {@link BeanDefinition}s to register + */ void registerBeanDefinitions(List beanDefinitions); + /** + * Processes all pending change events and activates the corresponding beans. + * + *

Thread–safety model:

+ *
    + *
  • This method must be called by exactly one thread.
  • + *
  • No other thread may call {@link #start()} concurrently.
  • + *
  • All mutating work on the registry (creation, modification, deletion, + * activation of beans) is performed exclusively inside this method.
  • + *
+ * + *

Execution model:

+ *
    + *
  • This method acts as the single consumer of the internal change-event queue.
  • + *
  • It blocks until the queue is empty and processes all events in order.
  • + *
  • The activation logic uses the insertion order of queued events to guarantee a + * deterministic activation sequence.
  • + *
  • Callers are responsible for scheduling this method in a dedicated thread when used + * with asynchronous producers (such as Kubernetes watchers).
  • + *
+ * + *

Usage constraints:

+ *
    + *
  • Call this exactly once during startup for static configuration.
  • + *
  • In Kubernetes mode, run this method in a dedicated long-running thread to consume + * update events as they arrive.
  • + *
+ */ + void start(); + Grammar getGrammar(); } diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java index 35203f2ded..4d25b102dc 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java @@ -35,27 +35,80 @@ public class BeanRegistryImplementation implements BeanRegistry { /** * TODO Rename give meaningful name */ - private final ConcurrentHashMap uuidMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap uuidMap = new ConcurrentHashMap<>(); // Order is here not critical private final BlockingQueue changeEvents = new LinkedBlockingDeque<>(); // uid -> bean definition - private final Map bds = new ConcurrentHashMap<>(); - private final Set uidsToActivate = ConcurrentHashMap.newKeySet(); + private final Map bds = new ConcurrentHashMap<>(); // Order is not critical. Order is determined by uidsToActivate + private final List uidsToActivate = new ArrayList<>(); public BeanRegistryImplementation(BeanCacheObserver observer, Grammar grammar) { this.observer = observer; this.grammar = grammar; } + /** + * Registers a set of {@link BeanDefinition}s originating from static configuration + * (for example YAML files on startup). + * + *

Thread–safety model:

+ *
    + *
  • This method is expected to be called from a single thread.
  • + *
  • It publishes initialization events into the internal change queue but does not + * perform any activation work itself.
  • + *
  • Callers must ensure that no other thread invokes {@link #registerBeanDefinitions(List)} + * concurrently.
  • + *
  • Dynamic updates (e.g. from Kubernetes watchers) must use {@link #handle(WatchAction, JsonNode)} + * instead; these calls are thread-safe because they only enqueue events.
  • + *
+ * + *

Lifecycle expectations:

+ *
    + *
  • After publishing all static configuration events, this method signals that + * the configuration stream has finished by inserting a {@code StaticConfigurationLoaded} + * marker event.
  • + *
  • The caller must invoke {@link #start()} exactly once after registration to process all + * pending events and activate the beans.
  • + *
+ * + * @param bds the list of static {@link BeanDefinition}s to register + */ public void registerBeanDefinitions(List bds) { bds.forEach(bd -> handle(ADDED, bd)); fireConfigurationLoaded(); // Only put event in the queue - start(); } /** - * Blocks until all events have been processed. For Kubernets use that block in a separate thread e.g. in KubernetsWatcher. + * Processes all pending change events and activates the corresponding beans. + * + *

Thread–safety model:

+ *
    + *
  • This method must be called by exactly one thread.
  • + *
  • No other thread may call {@link #start()} concurrently.
  • + *
  • All mutating work on the registry (creation, modification, deletion, + * activation of beans) is performed exclusively inside this method.
  • + *
  • Other threads may safely invoke {@link #handle(WatchAction, JsonNode)} or + * {@link #handle(WatchAction, BeanDefinition)}; these methods only enqueue events + * and do not perform activation.
  • + *
+ * + *

Execution model:

+ *
    + *
  • This method acts as the single consumer of the internal change-event queue.
  • + *
  • It blocks until the queue is empty and processes all events in order.
  • + *
  • The activation logic uses the insertion order of queued events to guarantee a + * deterministic activation sequence.
  • + *
  • Callers are responsible for scheduling this method in a dedicated thread when used + * with asynchronous producers (such as Kubernetes watchers).
  • + *
+ * + *

Usage constraints:

+ *
    + *
  • Call this exactly once during startup for static configuration.
  • + *
  • In Kubernetes mode, run this method in a dedicated long-running thread to consume + * update events as they arrive.
  • + *
*/ public void start() { while (!changeEvents.isEmpty()) { diff --git a/annot/src/test/java/com/predic8/membrane/annot/util/YamlParser.java b/annot/src/test/java/com/predic8/membrane/annot/util/YamlParser.java index 1f85717859..aa6a5e6629 100644 --- a/annot/src/test/java/com/predic8/membrane/annot/util/YamlParser.java +++ b/annot/src/test/java/com/predic8/membrane/annot/util/YamlParser.java @@ -50,6 +50,7 @@ public YamlParser(String resourceName) throws ClassNotFoundException, NoSuchMeth beanRegistry = new BeanRegistryImplementation(getLatchObserver(cdl),generator); beanRegistry.registerBeanDefinitions(GenericYamlParser.parseMembraneResources( requireNonNull(cl.getResourceAsStream(normalized)), generator)); + beanRegistry.start(); cdl.await(); } diff --git a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java index a85388aa79..86d9a90f58 100644 --- a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java +++ b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java @@ -156,7 +156,9 @@ private static Router initRouterByYAML(String location) throws Exception { router.setAsynchronousInitialization(true); router.start(); - new BeanRegistryImplementation(router, new GrammarAutoGenerated()).registerBeanDefinitions(parseMembraneResources(router.getResolverMap().resolve(location), new GrammarAutoGenerated())); + BeanRegistry registry = new BeanRegistryImplementation(router, new GrammarAutoGenerated()); + registry.registerBeanDefinitions(parseMembraneResources(router.getResolverMap().resolve(location), new GrammarAutoGenerated())); + registry.start(); return router; } diff --git a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java index e7772ef7db..a4fe5b72a6 100644 --- a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java @@ -345,6 +345,11 @@ public void registerBeanDefinitions(List beanDefinitions) { } + @Override + public void start() { + + } + @Override public Grammar getGrammar() { return null; From d6c2095edf767023ffcb38156df96d885d36db3b Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 8 Dec 2025 22:13:50 +0100 Subject: [PATCH 2/5] refactor: simplify Javadoc to avoid redundant method references in BeanRegistry --- .../java/com/predic8/membrane/annot/yaml/BeanRegistry.java | 5 ++--- .../membrane/annot/yaml/BeanRegistryImplementation.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java index b0bce7a341..cc07948783 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java @@ -32,8 +32,7 @@ public interface BeanRegistry { *
  • This method is expected to be called from a single thread.
  • *
  • It publishes initialization events into the internal change queue but does not * perform any activation work itself.
  • - *
  • Callers must ensure that no other thread invokes {@link #registerBeanDefinitions(List)} - * concurrently.
  • + *
  • Callers must ensure that no other thread invokes this method concurrently.
  • * * *

    Lifecycle expectations:

    @@ -55,7 +54,7 @@ public interface BeanRegistry { *

    Thread–safety model:

    *
      *
    • This method must be called by exactly one thread.
    • - *
    • No other thread may call {@link #start()} concurrently.
    • + *
    • No other thread may call this method concurrently.
    • *
    • All mutating work on the registry (creation, modification, deletion, * activation of beans) is performed exclusively inside this method.
    • *
    diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java index 4d25b102dc..b90fbba86a 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java @@ -57,7 +57,7 @@ public BeanRegistryImplementation(BeanCacheObserver observer, Grammar grammar) { *
  • This method is expected to be called from a single thread.
  • *
  • It publishes initialization events into the internal change queue but does not * perform any activation work itself.
  • - *
  • Callers must ensure that no other thread invokes {@link #registerBeanDefinitions(List)} + *
  • Callers must ensure that no other thread invokes this method * concurrently.
  • *
  • Dynamic updates (e.g. from Kubernetes watchers) must use {@link #handle(WatchAction, JsonNode)} * instead; these calls are thread-safe because they only enqueue events.
  • @@ -85,7 +85,7 @@ public void registerBeanDefinitions(List bds) { *

    Thread–safety model:

    *
      *
    • This method must be called by exactly one thread.
    • - *
    • No other thread may call {@link #start()} concurrently.
    • + *
    • No other thread may call this method concurrently.
    • *
    • All mutating work on the registry (creation, modification, deletion, * activation of beans) is performed exclusively inside this method.
    • *
    • Other threads may safely invoke {@link #handle(WatchAction, JsonNode)} or @@ -145,7 +145,6 @@ public void handle(WatchAction action, JsonNode node) { /** * May be called from multiple threads. - * * TODO remove action? */ public void handle(WatchAction action, BeanDefinition bd) { From 828b85323a564ad96754edde53f51a778012f3f7 Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 8 Dec 2025 22:32:48 +0100 Subject: [PATCH 3/5] refactor: replace `List` with `Set` for `uidsToActivate` to ensure ordering and updated `GrammarAutoGenerated` handling in `RouterCLI` --- .../membrane/annot/yaml/BeanRegistryImplementation.java | 2 +- .../main/java/com/predic8/membrane/core/cli/RouterCLI.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java index b90fbba86a..a9e5518d5c 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java @@ -41,7 +41,7 @@ public class BeanRegistryImplementation implements BeanRegistry { // uid -> bean definition private final Map bds = new ConcurrentHashMap<>(); // Order is not critical. Order is determined by uidsToActivate - private final List uidsToActivate = new ArrayList<>(); + private final Set uidsToActivate = new LinkedHashSet<>(); // Provides order public BeanRegistryImplementation(BeanCacheObserver observer, Grammar grammar) { this.observer = observer; diff --git a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java index 86d9a90f58..6e772c96ff 100644 --- a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java +++ b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java @@ -156,8 +156,9 @@ private static Router initRouterByYAML(String location) throws Exception { router.setAsynchronousInitialization(true); router.start(); - BeanRegistry registry = new BeanRegistryImplementation(router, new GrammarAutoGenerated()); - registry.registerBeanDefinitions(parseMembraneResources(router.getResolverMap().resolve(location), new GrammarAutoGenerated())); + GrammarAutoGenerated grammar = new GrammarAutoGenerated(); + BeanRegistry registry = new BeanRegistryImplementation(router, grammar); + registry.registerBeanDefinitions(parseMembraneResources(router.getResolverMap().resolve(location), grammar)); registry.start(); return router; From 3729a162f5c1486926eee9ec09ff747262a753ff Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Tue, 9 Dec 2025 12:58:54 +0100 Subject: [PATCH 4/5] refactor: update `start()` method Javadoc to clarify usage, thread-safety, and execution flow --- .../membrane/annot/yaml/BeanRegistry.java | 19 +++++++--------- .../yaml/BeanRegistryImplementation.java | 22 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java index cc07948783..fa2748d5f5 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java @@ -47,9 +47,8 @@ public interface BeanRegistry { * @param beanDefinitions the list of static {@link BeanDefinition}s to register */ void registerBeanDefinitions(List beanDefinitions); - - /** - * Processes all pending change events and activates the corresponding beans. +/** + * Processes all currently pending change events and activates the corresponding beans. * *

      Thread–safety model:

      *
        @@ -61,19 +60,17 @@ public interface BeanRegistry { * *

        Execution model:

        *
          - *
        • This method acts as the single consumer of the internal change-event queue.
        • - *
        • It blocks until the queue is empty and processes all events in order.
        • + *
        • This method processes all events currently in the queue and then returns.
        • + *
        • It does not block waiting for future events; it exits when the queue becomes empty.
        • *
        • The activation logic uses the insertion order of queued events to guarantee a * deterministic activation sequence.
        • - *
        • Callers are responsible for scheduling this method in a dedicated thread when used - * with asynchronous producers (such as Kubernetes watchers).
        • *
        * - *

        Usage constraints:

        + *

        Usage:

        *
          - *
        • Call this exactly once during startup for static configuration.
        • - *
        • In Kubernetes mode, run this method in a dedicated long-running thread to consume - * update events as they arrive.
        • + *
        • Call this exactly once during startup after {@link #registerBeanDefinitions(List)} + * to process static configuration.
        • + *
        • Do not call this method repeatedly or in a loop; it is not designed as a long-running consumer.
        • *
        */ void start(); diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java index a9e5518d5c..5b6d93c696 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java @@ -80,7 +80,7 @@ public void registerBeanDefinitions(List bds) { } /** - * Processes all pending change events and activates the corresponding beans. + * Processes all currently pending change events and activates the corresponding beans. * *

        Thread–safety model:

        *
          @@ -88,26 +88,24 @@ public void registerBeanDefinitions(List bds) { *
        • No other thread may call this method concurrently.
        • *
        • All mutating work on the registry (creation, modification, deletion, * activation of beans) is performed exclusively inside this method.
        • - *
        • Other threads may safely invoke {@link #handle(WatchAction, JsonNode)} or - * {@link #handle(WatchAction, BeanDefinition)}; these methods only enqueue events - * and do not perform activation.
        • *
        * *

        Execution model:

        *
          - *
        • This method acts as the single consumer of the internal change-event queue.
        • - *
        • It blocks until the queue is empty and processes all events in order.
        • + *
        • This method processes all events currently in the queue and then returns.
        • + *
        • It does not block waiting for future events; it exits when the queue becomes empty.
        • *
        • The activation logic uses the insertion order of queued events to guarantee a * deterministic activation sequence.
        • - *
        • Callers are responsible for scheduling this method in a dedicated thread when used - * with asynchronous producers (such as Kubernetes watchers).
        • + *
        • After initial startup, dynamic configuration updates (e.g., from Kubernetes watchers) + * are processed immediately in {@link #handle(BeanDefinition)} when the queue is empty, + * bypassing this method.
        • *
        * - *

        Usage constraints:

        + *

        Usage:

        *
          - *
        • Call this exactly once during startup for static configuration.
        • - *
        • In Kubernetes mode, run this method in a dedicated long-running thread to consume - * update events as they arrive.
        • + *
        • Call this exactly once during startup after {@link #registerBeanDefinitions(List)} + * to process static configuration.
        • + *
        • Do not call this method repeatedly or in a loop; it is not designed as a long-running consumer.
        • *
        */ public void start() { From 352486910e4d161141cf8af2f8d049d7861651ec Mon Sep 17 00:00:00 2001 From: Tobias Polley Date: Wed, 10 Dec 2025 10:50:29 +0100 Subject: [PATCH 5/5] remove inconsistent comments --- .../membrane/annot/yaml/BeanRegistry.java | 50 +---------------- .../yaml/BeanRegistryImplementation.java | 55 ------------------- 2 files changed, 1 insertion(+), 104 deletions(-) diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java index fa2748d5f5..0fbae75dcb 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistry.java @@ -23,56 +23,8 @@ public interface BeanRegistry { List getBeans(); - /** - * Registers a set of {@link BeanDefinition}s originating from static configuration - * (for example YAML files on startup). - * - *

        Thread–safety model:

        - *
          - *
        • This method is expected to be called from a single thread.
        • - *
        • It publishes initialization events into the internal change queue but does not - * perform any activation work itself.
        • - *
        • Callers must ensure that no other thread invokes this method concurrently.
        • - *
        - * - *

        Lifecycle expectations:

        - *
          - *
        • After publishing all static configuration events, this method signals that - * the configuration stream has finished by inserting a {@code StaticConfigurationLoaded} - * marker event.
        • - *
        • The caller must invoke {@link #start()} exactly once after registration to process all - * pending events and activate the beans.
        • - *
        - * - * @param beanDefinitions the list of static {@link BeanDefinition}s to register - */ void registerBeanDefinitions(List beanDefinitions); -/** - * Processes all currently pending change events and activates the corresponding beans. - * - *

        Thread–safety model:

        - *
          - *
        • This method must be called by exactly one thread.
        • - *
        • No other thread may call this method concurrently.
        • - *
        • All mutating work on the registry (creation, modification, deletion, - * activation of beans) is performed exclusively inside this method.
        • - *
        - * - *

        Execution model:

        - *
          - *
        • This method processes all events currently in the queue and then returns.
        • - *
        • It does not block waiting for future events; it exits when the queue becomes empty.
        • - *
        • The activation logic uses the insertion order of queued events to guarantee a - * deterministic activation sequence.
        • - *
        - * - *

        Usage:

        - *
          - *
        • Call this exactly once during startup after {@link #registerBeanDefinitions(List)} - * to process static configuration.
        • - *
        • Do not call this method repeatedly or in a loop; it is not designed as a long-running consumer.
        • - *
        - */ + void start(); Grammar getGrammar(); diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java index 5b6d93c696..7ed672c7e0 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java @@ -48,66 +48,11 @@ public BeanRegistryImplementation(BeanCacheObserver observer, Grammar grammar) { this.grammar = grammar; } - /** - * Registers a set of {@link BeanDefinition}s originating from static configuration - * (for example YAML files on startup). - * - *

        Thread–safety model:

        - *
          - *
        • This method is expected to be called from a single thread.
        • - *
        • It publishes initialization events into the internal change queue but does not - * perform any activation work itself.
        • - *
        • Callers must ensure that no other thread invokes this method - * concurrently.
        • - *
        • Dynamic updates (e.g. from Kubernetes watchers) must use {@link #handle(WatchAction, JsonNode)} - * instead; these calls are thread-safe because they only enqueue events.
        • - *
        - * - *

        Lifecycle expectations:

        - *
          - *
        • After publishing all static configuration events, this method signals that - * the configuration stream has finished by inserting a {@code StaticConfigurationLoaded} - * marker event.
        • - *
        • The caller must invoke {@link #start()} exactly once after registration to process all - * pending events and activate the beans.
        • - *
        - * - * @param bds the list of static {@link BeanDefinition}s to register - */ public void registerBeanDefinitions(List bds) { bds.forEach(bd -> handle(ADDED, bd)); fireConfigurationLoaded(); // Only put event in the queue } - /** - * Processes all currently pending change events and activates the corresponding beans. - * - *

        Thread–safety model:

        - *
          - *
        • This method must be called by exactly one thread.
        • - *
        • No other thread may call this method concurrently.
        • - *
        • All mutating work on the registry (creation, modification, deletion, - * activation of beans) is performed exclusively inside this method.
        • - *
        - * - *

        Execution model:

        - *
          - *
        • This method processes all events currently in the queue and then returns.
        • - *
        • It does not block waiting for future events; it exits when the queue becomes empty.
        • - *
        • The activation logic uses the insertion order of queued events to guarantee a - * deterministic activation sequence.
        • - *
        • After initial startup, dynamic configuration updates (e.g., from Kubernetes watchers) - * are processed immediately in {@link #handle(BeanDefinition)} when the queue is empty, - * bypassing this method.
        • - *
        - * - *

        Usage:

        - *
          - *
        • Call this exactly once during startup after {@link #registerBeanDefinitions(List)} - * to process static configuration.
        • - *
        • Do not call this method repeatedly or in a loop; it is not designed as a long-running consumer.
        • - *
        - */ public void start() { while (!changeEvents.isEmpty()) { try {