Kodein + Ktor = mutation attempt of frozen kotlin.collections.HashMap - why? - kotlin

I have been struggling with this exception for couple of days recently.
I have a kotlin multiplatform project with these dependencies:
kotlin=1.5.10
kodein=7.6.0
ktor=1.6.0 (uses kotlin coroutines 1.5.0-native-mt internally)
And I have been getting mentioned exception while trying to use httpClient in native:
at kotlin.Throwable#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Throwable.kt:23)
at kotlin.Exception#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
at kotlin.RuntimeException#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
at kotlin.native.concurrent.InvalidMutabilityException#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22)
at <global>.ThrowInvalidMutabilityException(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:93)
at <global>.MutationCheck(Unknown Source)
at kotlin.collections.HashMap.<set-length>#internal(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:16)
at kotlin.collections.HashMap#addKey(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:292)
at kotlin.collections.HashMap#put(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:68)
at org.kodein.di.internal.DITreeImpl#find(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DITreeImpl.kt:132)
at org.kodein.di.DITree#find$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DITree.kt:36)
at org.kodein.di.internal.DIContainerImpl#factory(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DIContainerImpl.kt:158)
at org.kodein.di.DIContainer#factory$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:32)
at org.kodein.di.DIContainer#provider(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:76)
at org.kodein.di.DIContainer#provider$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:75)
at org.kodein.di.internal.DirectDIBaseImpl#Instance(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DirectDIImpl.kt:30)
at InvalidMutabilitySampleTest.<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2#internal(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:26)
at InvalidMutabilitySampleTest.$<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2$FUNCTION_REFERENCE$3.invoke#internal(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:25)
at InvalidMutabilitySampleTest.$<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2$FUNCTION_REFERENCE$3.$<bridge-UNNN>invoke(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:25)
at io.ktor.client.HttpClientConfig.install$<anonymous>_1#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:69)
at io.ktor.client.HttpClientConfig.$install$<anonymous>_1$FUNCTION_REFERENCE$17.invoke#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:65)
at io.ktor.client.HttpClientConfig.$install$<anonymous>_1$FUNCTION_REFERENCE$17.$<bridge-UNNN>invoke(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:65)
at io.ktor.client.features.json.JsonFeature.Feature#prepare(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-features/ktor-client-json/common/src/io/ktor/client/features/json/JsonFeature.kt:129)
at io.ktor.client.HttpClientConfig.install$<anonymous>_1-2#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:77)
at io.ktor.client.HttpClientConfig.$install$<anonymous>_1-2$FUNCTION_REFERENCE$18.invoke#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:74)
at io.ktor.client.HttpClientConfig.$install$<anonymous>_1-2$FUNCTION_REFERENCE$18.$<bridge-UNNN>invoke(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:74)
at io.ktor.client.HttpClientConfig#install(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:97)
at io.ktor.client.HttpClient#<init>(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:172)
at io.ktor.client.HttpClient#<init>(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:81)
at io.ktor.client#HttpClient(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:43)
I managed to reproduce that crash in a test:
class InvalidMutabilitySampleTest {
val di = DI {
import(DI.Module("Some Module") {
bind<Json>() with provider {
Json {
prettyPrint = true
isLenient = true
}
}
bind<HttpClient>() with provider {
HttpClient{
install(JsonFeature) {
serializer = KotlinxSerializer(instance())
}
}
}
})
}
val httpClient: HttpClient by di.instance()
#Test
fun invalidMutabilityTest() {
println(httpClient)
}
}
I also managed to fix it by changing:
HttpClient{
install(JsonFeature) {
serializer = KotlinxSerializer(instance())
}
}
to:
val json = instance<Json>()
HttpClient{
install(JsonFeature) {
serializer = KotlinxSerializer(json)
}
}
Then I noticed that HttpClient does a very specific thing - it freezes itself in the init block.
I managed to reproduce it with this sample code:
class FrozenConstructor(val block: ()->Unit) {
init {
freeze()
}
}
class InvalidMutabilitySampleTest2 {
val di = DI {
import(DI.Module("Some Module") {
bind<String>() with provider {
"lolo"
}
bind<FrozenConstructor>() with provider {
FrozenConstructor{
instance<String>()
}
}
})
}
val frozenConstructor: FrozenConstructor by di.instance()
#Test
fun invalidMutabilityTest() {
println(frozenConstructor)
}
}
So my theory is following:
kodein tries to provide FrozenConstructor class
FrozenConstructor is created and it freezes itself, and its member which references kodein
kodein tries to cache provided dependency and try to mutate internal MutableMap which is frozen and everything crashes
Can someone confirm that this is more or less correct , and correct me if not?
And also can you guys suggest the best way of handling it, and other pitfalls which are waiting there?
Is it a kodein bug?
Why kodein has to store something in a mutable map if I am using with provider not with singleton ?

I think you've pretty much figured it out. Ktor freezes itself and all of its configuration in order to ensure that it can be used across threads in Kotlin/Native. Kodein assumes you will only ever touch it from one thread and is not safe to freeze. (Whether that's a limitation, a bug, or a design flaw is maybe up for interpretation.)
To work around these issues, you want to avoid accidentally capturing this references in your HttpClient configuration that refer to Kodein internals. A good way to do this is to grab instances from DI in helper variables outside the HttpClient lambda, as you've discovered.

You are correct.
FrozenConstructor freezes itself, and therefore its block property, which, as a lambda, freezes all its captures, which includes the DI container.
Kodein-DI does not support freezing (and therefore native multi-threading).
The decision has been made not to invest in making it compatible with native multi-thrading since JB has announced working on a new GC.

Related

Compose Desktop testing - how to check if something is visible?

Given some simple content:
#Composable
fun MyContent() {
var showThing by remember { mutableStateOf(false) }
if (showThing) {
Box(Modifier.testTag("thing")) {
Text("The Thing")
}
}
}
If I try to test whether the thing has been displayed:
#OptIn(ExperimentalTestApi::class)
class Scratch {
#get:Rule
val compose = createComposeRule()
#Test
fun test() {
runBlocking(Dispatchers.Main) {
compose.setContent {
MyContent()
}
compose.awaitIdle()
compose.onNodeWithTag("thing").assertIsNotDisplayed()
}
}
}
I get this:
An operation is not implemented.
kotlin.NotImplementedError: An operation is not implemented.
at androidx.compose.ui.test.DesktopAssertions_desktopKt.checkIsDisplayed(DesktopAssertions.desktop.kt:23)
at androidx.compose.ui.test.AssertionsKt.assertIsNotDisplayed(Assertions.kt:49)
at Scratch$test$1.invokeSuspend(Scratch.kt:44)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
...
I thought testing whether something was displayed or not would be the most basic thing to test, but it isn't supported by the framework yet. The test framework is experimental, so I was expecting to find things missing, but not like this.
Is there another way to do this which I'm missing? All the tutorials out there talk about assertIsDisplayed() being the way, but maybe there is an alternative?
It's not a direct substitute, but unfortunately, JB Compose Desktop has these limitations in the UI test suite. Aside from using only JUnit 4, and not being compatible with the newer version, many assertion methods and also screen interaction methods are not implemented, such as the .assertIsNotDisplayed() that you tried to use, and also actions like .performTextInput().
An alternative for your problem would be using other methods like .assertDoesNotExist() and .assertExists().
It's not going to tell you if the element is in the boundaries of the screen and visible, but at least will tell you that your node exists and is instantiated, which is something, and it's better than nothing.
Until JetBrains implement the complete desktop test suite, we need to work with what we have, or maybe try implementing some things as a workaround.
In your case, this will work:
#OptIn(ExperimentalTestApi::class)
class Scratch {
#get:Rule
val compose = createComposeRule()
#Test
fun test() {
runBlocking(Dispatchers.Main) {
compose.setContent {
MyContent()
}
compose.awaitIdle()
compose.onNodeWithTag("thing").assertDoesNotExist()
}
}

Axonframework, how to use MessageDispatchInterceptor with reactive repository

I have read the set-based consistency validation blog and I want to validate through a dispatch interceptor. I follow the example, but I use reactive repository and it doesn't really work for me. I have tried both block and not block. with block it throws error, but without block it doesn't execute anything. here is my code.
class SubnetCommandInterceptor : MessageDispatchInterceptor<CommandMessage<*>> {
#Autowired
private lateinit var privateNetworkRepository: PrivateNetworkRepository
override fun handle(messages: List<CommandMessage<*>?>): BiFunction<Int, CommandMessage<*>, CommandMessage<*>> {
return BiFunction<Int, CommandMessage<*>, CommandMessage<*>> { index: Int?, command: CommandMessage<*> ->
if (CreateSubnetCommand::class.simpleName == (command.payloadType.simpleName)){
val interceptCommand = command.payload as CreateSubnetCommand
privateNetworkRepository
.findById(interceptCommand.privateNetworkId)
// ..some validation logic here ex.
// .filter { network -> network.isSubnetOverlap() }
.switchIfEmpty(Mono.error(IllegalArgumentException("Requested subnet is overlap with the previous subnet.")))
// .block() also doesn't work here it throws error
// block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-
}
command
}
}
}
Subscribing to a reactive repository inside a message dispatcher is not really recommended and might lead to weird behavior as underling ThreadLocal (used by Axox) is not adapted to be used in reactive programing
Instead, check out Axon's Reactive Extension and reactive interceptors section.
For example what you might do:
reactiveCommandGateway.registerDispatchInterceptor(
cmdMono -> cmdMono.flatMap(cmd->privateNetworkRepository
.findById(cmd.privateNetworkId))
.switchIfEmpty(
Mono.error(IllegalArgumentException("Requested subnet is overlap with the previous subnet."))
.then(cmdMono)));

Resiliency4j CircuitBreaker tried to call circuitBreaker logic in AOP in order to achieve not to call circuit breaker when it is disabled in config

Conditionally I want to switch the circuit breaker switch off/on by setting spring.cloud.circuitbreaker.resilience4j.enabled=false. My logic should stay intact from circuit-breaker logic.
I tried using the below demo example to extend to my requirements, I am trying to bind circuit breaker call on target method based on circuit breaker flag spring.cloud.circuitbreaker.resilience4j.enabled=true in application.property, true and false case. There could be a simpler way to achieve this, help me if any other solution than what I tried.
Example:
spring cloud circuit-breaker-resiliency4j example
Tried calling happy path - Work fine when there is no exception [response comes within 3 seconds as time limiter set to 3seconds in bean creation]
application.properties:
spring.cloud.circuitbreaker.resilience4j.enabled=true
spring.cloud.config.enabled=false
spring.cloud.config.import-check.enabled=false
spring.main.allow-bean-definition-overriding=true
Controller:
#GetMapping("/delay/{seconds}")
public Map delay(#PathVariable int seconds) {
return mockService.delay(seconds);
}
MockService:
#ApplyCircuitBreaker
public Map delay(int seconds) {
return rest.getForObject("https://httpbin.org/delay/" + seconds, Map.class);
}
Config class:
#Configuration
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled"}, matchIfMissing = true)
public class ResiliencyConfig {
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.build());
}
}
ApplyCircuitBreaker - Custom annotation to Apply circuit breaker only for required methods:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ApplyCircuitBreaker {
}
AOP: CircuitBreakerAroundAspect:
#Aspect
#Component
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
public class CircuitBreakerAroundAspect {
#Autowired
CircuitBreakerFactory circuitBreakerFactory;
#Around("#annotation(com.ravibeli.circuitbreaker.aspects.ApplyCircuitBreaker)")
public Object aroundAdvice(final ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Arguments passed to method are: {}", Arrays.toString(joinPoint.getArgs()));
AtomicReference<Map<String, String>> fallback = new AtomicReference<>();
Object proceed = circuitBreakerFactory.create(joinPoint.getSignature().toString())
.run(() -> {
try {
log.info("Inside CircuitBreaker logic in Aspect");
return joinPoint.proceed();
} catch (Throwable t) {
log.error(t.getMessage());
}
return null;
}, Throwable::getMessage);
log.info("Result from method is: {}", proceed);
return proceed;
}
}
My requirement:
circuitBreakerFactory.create(joinPoint.getSignature().toString()) .run(() -> ....) at this line, when target method throws exception, controll should go to fallback mechanism call. Since joinPoint.proceed() throws exception, it is forcing to handle exception - So I am doing wrong here, need suggestion to fix this to solve the requirement.
Error log:
{
"timestamp": "2021-07-10T01:33:10.558+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')\r\n\tat com.ravibeli.circuitbreaker.service.MockService$$EnhancerBySpringCGLIB$$3e293bd0.delay(<generated>)\r\n\tat com.ravibeli.circuitbreaker.controllers.DemoController.delay(DemoController.java:53)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
"message": "class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')",
"path": "/delay/3"
}
You seem to be asking a couple different questions here.
The title seems to be asking why the aspect is still present when
spring.cloud.circuitbreaker.resilience4j.enabled=false
The problem is with your conditional
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
It's simply requiring the property be present, it's not checking what it's set to. You need to set havingValue=true as well.
That said, I would strongly suggest not making your own pointcut for circuit breakers. Use the annotations provided by Resiliancy4j and just specify the fallback method there. I would expect that to clear up any other issues you're having with fallbacks.
#Bulkhead(name = 'myService', fallbackMethod = "myFallback")
#CircuitBreaker(name = 'myService', fallbackMethod = "myFallback")
#RateLimiter(name = 'myService', fallbackMethod = "myFallback")
#TimeLimiter(name = 'myService', fallbackMethod = "myFallback")
For enabling the circuit breaker dynamically you can use Profiles or Externalized Configuration (preferred approach would be to use Profiles and you can google more about them)
As far as your aspect's code goes, it looks and runs fine for me. Link to Code. It would be better if you could share the link to the code-base so that the issue can be investigated a bit further. Nevertheless, it seems a minor issue.
Thanks, guys for your comments, got the simple idea to fix this.
I resolved it with a custom factory implementation to make enable/disable feature working.
My GitHub example code: spring-cloud-resiliency4j

Receiving ResponseAlreadySentException after attempting to respond to ApplicationCall objects passed through StateFlow

I am testing an event driven architecture in KTOR. My Core logic is held in a class that reacts to different Event types being emitted by a StateFlow. EventGenerators push Events into the StateFlow which are picked up by the Core.
However, when the Core attempts to respond to an ApplicationCall embedded in one of my Events I receive an ResponseAlreadySentException and I'm not sure why this would be the case. This does not happen if I bypass the StateFlow and call the Core class directly from the EventGenerator. I am not responding to ApplicationCalls anywhere else in my code, and have checked with breakpoints that the only .respond line is not being hit multiple times.
MyStateFlow class:
class MyStateFlow {
val state: StateFlow<CoreEvent>
get() = _state
private val _state = MutableStateFlow<CoreEvent>(CoreEvent.NothingEvent)
suspend fun update(event: CoreEvent) {
_state.value = event
}
}
My Core class:
class Core(
myStateFlow: MyStateFlow,
coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.IO
) {
init {
CoroutineScope(coroutineContext).launch {
myStateFlow.state.collect {
onEvent(it)
}
}
}
suspend fun onEvent(event: CoreEvent) {
when(event) {
is FooEvent {
event.call.respond(HttpStatusCode.OK, "bar")
}
...
}
}
}
One of my EventGenerators is a Route in my KTOR Application class:
get("/foo") {
myStateFlow.update(CoreEvent.FooEvent(call))
}
However, hitting /f00 in my browser returns either an ResponseAlreadySentException or an java.lang.UnsupportedOperationException with message: "Headers can no longer be set because response was already completed". The error response can flip between the two while I'm tinkering with different attempted solutions, but they seem to be saying the same thing: The call has already been responded to before I attempt to call call.respond(...).
If I change my Route instead to call the Core.onEvent() directly, hitting /foo returns "bar" in my browser as is the intended behaviour:
get("/foo") {
core.onEvent(CoreEvent.FooEvent(call))
}
For completeness, my dependency versions are:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "io.ktor:ktor-server-netty:1.4.1"
Thank you in advanced for any insight you can offer.

Why is lazy variable initialised multiple times per thread

I am working on some code where I create a HikariDataSource as a by lazy value. I did apply the mode LazyThreadSafetyMode.SYNCHRONIZED, so I am confused as to why the initialization is done twice when I spin up a new thread that reads that value
// in module database
val dataSource: HikariDataSource by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
HikariDataSource(dataSourceConfig(databaseConfig))
}
fun initDatabase() {
dataSource.connect().let { //do stuff}
}
// in main module
fun main() {
initDatabase()
thread {
dataSource.connect().let { // initializes a new data source... }
}
}
I would expect the HikariDataSource to be initialized once, but for some reason its called twice...
I think the fact that I am exposing that variable to another gradle project might affect it, but not sure why..
Well, I think I just answered my own question...
The main module that access the dataSource object is a ktor server that was configured for hot reload..
As soon as I disabled the hotreload and tried again, the variable was not initialized twice.
I guess ktor is doing some stuff with the classloader for hot reload that causes this thing to happen.