quarkiverse / quarkus-unleash

Unleash is a open source feature flag & toggle system
https://github.com/Unleash/unleash
Apache License 2.0
17 stars 9 forks source link

Allow deactivating the extension at runtime #227

Closed gwenneg closed 8 months ago

gwenneg commented 8 months ago

Fixes #219. Fixes #223.

This PR introduces quarkus.unleash.active=true|false which can be used to disable the extension at run time and is set to true by default. In other words, the extension behavior should not change for existing apps after this is merged.

gwenneg commented 8 months ago

I'll promote the PR for review when I'm done running some manual tests.

gwenneg commented 8 months ago

I did NOT keep the old code which prevented the Unleash client from being shut down when Quarkus is reloaded in dev mode: https://github.com/quarkiverse/quarkus-unleash/blob/28f2bc18dd3f9192ff45158ce2a1e739a8277db0/runtime/src/main/java/io/quarkiverse/unleash/runtime/UnleashService.java#L36-L45

@andrejpetras Please let me know if there was a specific reason why that was initially implemented.

gwenneg commented 8 months ago

@gsmet Does this PR match what you had in mind about the synthetic bean?

gwenneg commented 8 months ago

I guess this is why the Unleash client should not be shut down in dev mode:

2024-03-04 16:07:10,297 INFO  [io.get.rep.FeatureBackupHandlerFile] (Quarkus Main Thread) Unleash will try to load feature toggle states from temporary backup
2024-03-04 16:07:10,300 INFO  [io.get.rep.ToggleBootstrapFileProvider] (Quarkus Main Thread) Trying to read feature toggles from bootstrap file found at null
2024-03-04 16:07:10,307 ERROR [io.get.uti.UnleashScheduledExecutorImpl] (Quarkus Main Thread) Unleash background task crashed: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@1f2acb49[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@38f0c19b[Wrapped task = io.getunleash.repository.FeatureRepository$$Lambda$1236/0x00000008012ac418@538b5054]] rejected from java.util.concurrent.ScheduledThreadPoolExecutor@35354e61[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 8]
        at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
        at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:340)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(ScheduledThreadPoolExecutor.java:632)
        at io.getunleash.util.UnleashScheduledExecutorImpl.setInterval(UnleashScheduledExecutorImpl.java:43)
        at io.getunleash.repository.FeatureRepository.initCollections(FeatureRepository.java:112)
        at io.getunleash.repository.FeatureRepository.<init>(FeatureRepository.java:49)
        at io.getunleash.repository.FeatureRepository.<init>(FeatureRepository.java:33)
        at io.getunleash.DefaultUnleash.defaultToggleRepository(DefaultUnleash.java:50)
        at io.getunleash.DefaultUnleash.<init>(DefaultUnleash.java:54)
        at io.quarkiverse.unleash.runtime.UnleashCreator.createUnleash(UnleashCreator.java:31)
        at io.quarkiverse.unleash.runtime.UnleashRecorder$1.get(UnleashRecorder.java:31)
        at io.quarkiverse.unleash.runtime.UnleashRecorder$1.get(UnleashRecorder.java:28)
        at io.quarkus.arc.runtime.ArcRecorder$4.apply(ArcRecorder.java:129)
        at io.quarkus.arc.runtime.ArcRecorder$4.apply(ArcRecorder.java:126)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.createSynthetic(Unknown Source)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.doCreate(Unknown Source)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.create(Unknown Source)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.create(Unknown Source)
        at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:119)
        at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:38)
        at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:35)
        at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:32)
        at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
        at io.quarkus.arc.impl.ComputingCacheContextInstances.computeIfAbsent(ComputingCacheContextInstances.java:19)
        at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:35)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.get(Unknown Source)
        at io.getunleash.Unleash_1dUsziLNibzmhB7vbUKV29eD9C4_Synthetic_Bean.get(Unknown Source)
        at io.quarkiverse.unleash.runtime.UnleashLifecycleManager_Observer_onStartup_ADcKH1UFty7zm_hpwYsrbo_EPPY.notify(Unknown Source)
        at io.quarkus.arc.impl.EventImpl$Notifier.notifyObservers(EventImpl.java:346)
        at io.quarkus.arc.impl.EventImpl$Notifier.notify(EventImpl.java:328)
        at io.quarkus.arc.impl.EventImpl.fire(EventImpl.java:82)
        at io.quarkus.arc.runtime.ArcRecorder.fireLifecycleEvent(ArcRecorder.java:155)
        at io.quarkus.arc.runtime.ArcRecorder.handleLifecycleEvents(ArcRecorder.java:106)
        at io.quarkus.deployment.steps.LifecycleEventsBuildStep$startupEvent1144526294.deploy_0(Unknown Source)
        at io.quarkus.deployment.steps.LifecycleEventsBuildStep$startupEvent1144526294.deploy(Unknown Source)
        at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
        at io.quarkus.runtime.Application.start(Application.java:101)
        at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
        at io.quarkus.runner.GeneratedMain.main(Unknown Source)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:113)
        at java.base/java.lang.Thread.run(Thread.java:833)
gwenneg commented 8 months ago

~The Unleash client should probably use an executor from Quarkus rather than the current one. That's the last thing I'm trying to change before this PR is ready for review.~

Let's deal with that in a subsequent issue/PR: #228.

gwenneg commented 8 months ago

This is ready for a review now! 🎉

andrejpetras commented 8 months ago

@gwenneg @gsmet

Please let me know if there was a specific reason why that was initially implemented.

That should work. I can't think of any reason why this should be the case

gwenneg commented 8 months ago

@andrejpetras @gsmet Do you want (or have time) to take another look before I'm merging this PR?

andrejpetras commented 8 months ago

LGTM