Spring Boot Webflux and suspend functions cannot be evaluated in Intellij Idea (this#HelloController is not captured) - spring-boot

In intellij idea evaluating (Cmd + F8 or Option + click) suspended functions while debugging works just fine.
The problem arises in combination with webflux and suspended functions.
Once suspended you cannot evaluate the captured variables any longer.
Error states: this#HelloController is not captured
One workaround is to create a coroutineScope around an endpoint.
Not sure why this is working at all and have not tested any performance impacts on this.
Searching for a proper way to be able to debug suspended endpoints in intellij with webflux and coroutines, without having to add this coroutineScope and an explanation why it's not working otherwise.
#RestController
class HelloController(
val helloService: HelloService,
) {
#GetMapping("hello")
suspend fun hello() =
helloService.hello() // Throws error when evaluated
#GetMapping("helloCoroutine")
suspend fun helloCoroutine() = coroutineScope {
helloService.hello() // Works just fine when evaluated
}
}
#Service
class HelloService {
suspend fun hello(): String {
delay(10)
return "hello there!"
}
}
Complete code can be found here: https://github.com/Ch4s3r/webflux_coroutines_test

Related

traceId is lost in a coroutine?

I am upgrading an application with Kotlin, Webflux to Spring Boot 3.
Now I noticed that our logs are lacking traceIds.
My suspicion is that this is due to coroutines, since I observed that the logs of the controller contain a traceId and in the logs of the service it is not included. The controller method as well as the service method both have the suspend keyword.
#PostMapping("/upload-file")
#Observed
suspend fun uploadFile(
serverWebExchange: ServerWebExchange,
): ResponseEntity<MessageResponse> {
logger.debug("uploadFile")
importService.handle(getMultipartDataPart("file", serverWebExchange))
return ResponseEntity(MessageResponse("Success."), OK)
}
#Service
class ImportService() {
suspend fun handle(fileAsByteArray: ByteArray) {
log.debug("Import file.")
...
}
I followed the documentation and added depdencies
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-tracing-bridge-brave")
Bean
#Bean
fun observedAspect(observationRegistry: ObservationRegistry?): ObservedAspect? {
return ObservedAspect(observationRegistry)
}
and Annotation on the controller method
#GetMapping("/keys/{key}")
#Observed
suspend fun isKeyInKeyStore(
Another interesting observation I made is that I don't observe the problem on my local machine, but only on our dev K8s cluster. Could a different JVM (OpenJDK vs AWS Corretto) cause this?
I am quite sure that the context is lost, but not sure how to deal with it in the best way since I am pretty new to coroutines and Webflux.

Why would one use runBlocking(IO) instead of just runBlocking in a Spring Boot app

I have a Spring Boot app and when handling a given request I need to call upstream services in a parallel and wait for the result to complete before returning them in my own response.
In the existing code base, I noticed that in order to do so, the pattern is to use runBlocking(IO) { ... }
#Service
class MyUpstreamService {
fun getSomething() = 1
}
#RestController
class MyController(
val upstream: MyUpstreamService
) {
#GetMapping("/foo")
fun foo() =
runBlocking(Dispatchers.IO) {
val a = async { upstream.getSomething() }
val b = async { upstream.getSomething() }
a.await() + b.await()
}
}
This works as expected.
Now for some reasons I need to set the scope of MyUpstreamService to #RequestScope and if I do so, I get the following exception as soon as I access MyUpstreamService from within the runBlocking(IO) { ... } block:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.3.22.jar:5.3.22]
If I do not use the Dispatchers.IO context, then everything works fine.
So the question is why would one use runBlocking(Dispatchers.IO) { .. } instead of just runBlocking { .. } when waiting for several async calls to complete?
For completeness, here is the entire snippet demonstrating the question.
GET /bar works
GET /foo throws the exception
#RequestScope
#Service
class MyUpstreamService(
// val currentUser: CurrentUser
) {
fun getSomething() = 1
}
#RestController
class MyController(
val upstream: MyUpstreamService
) {
#GetMapping("/foo")
fun foo() =
runBlocking(Dispatchers.IO) {
val a = async { upstream.getSomething() }
val b = async { upstream.getSomething() }
a.await() + b.await()
}
#GetMapping("/bar")
fun bar() =
runBlocking {
val a = async { upstream.getSomething() }
val b = async { upstream.getSomething() }
a.await() + b.await()
}
}
runBlocking without particular dispatcher means that all coroutines inside are launched in a special single-threaded event loop dispatcher backed by the thread that you're blocking. This, in turn, means your coroutines would not run in parallel.
runBlocking(Dispatchers.IO) means the nested coroutines run on the IO dispatcher, which is backed by a pool of threads of dynamic size, and thus the coroutines are effectively run in parallel (within some limit). At the same time, it's still a runBlocking, which means the calling thread would still be blocked while waiting for the nested coroutines to complete, but it would not be used to do any work.
for some reasons I need to set the scope of MyUpstreamService to #RequestScope
When you do this, Spring creates one service instance by request - and this is done based on the request's thread (by using some ThreadLocal machinery I assume). As we have just seen, runBlocking without dispatcher actually uses the calling thread, so the request thread, and that is why this mechanism still works. If you use runBlocking(IO) and dispatch on other threads, you're breaking this Spring mechanism.
Now I haven't done Spring dev in a while, so I'm not 100% sure how to fix your problem. But I believe a good start would be to stop using the thread-per-request model if you're using coroutines, and thus switch to suspend functions in your controllers using Spring WebFlux. I think it will still not allow to use #RequestScope, though, because you would be giving up the "request thread" concept altogether. See https://github.com/spring-projects/spring-framework/issues/28235

getting started with kotlin and SpringBootApplication to run some suspend fun

Trying to run this repo with some suspend functions. Can someone please give some hints?
Let say we have one
suspend fun log(){
mLog.subscribeAlways<GroupMessageEvent> { event ->
if (event.message.content.contains("Error")) {
print("****")
} else if (event.message.content.contains("Warning")) {
print("Warning")
}
}
mLog.Listen()
}
How can we trigger this log from main
open class Application {
companion object {
#JvmStatic fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
}
}
What have try, it can run without error, but it didn't work as expected,
call the log function from Controller class
class Controller {
#Value("\${spring.datasource.url}")
private var dbUrl: String? = null
#Autowired
lateinit private var dataSource: DataSource
#RequestMapping("/")
internal suspend fun index(): String {
mLog()
return "index"
}
Suspend functions should be called from a coroutine. There are several coroutine builder functions: launch, async, runBlocking.
Using these functions you can start a coroutine, for example:
runBlocking {
// call suspend methods
}
Coroutines launched with launch and async coroutine builders in a context of some CoroutineScope. They don't block the current thread. There are more info in the docs.
runBlocking blocks the current thread interruptibly until its completion.
Using launch coroutine builder you will not block the current thread if calling suspend function in it:
fun index(): String {
GlobalScope.launch {
log()
}
"index"
}
Note: in this case function index returns before log is executed. GlobalScope is discouraged to use. Application code usually should use an application-defined CoroutineScope. How to define a CoroutineScope is described in the docs and here and here.
If your intention is to block the current thread until suspend function completes use runBlocking coroutine builder:
fun index(): String = runBlocking {
log()
"index"
}
Some useful links: GlobalScope.launch vs runBlocking, Update UI async call with coroutines, Suspend function 'callGetApi' should be called only from a coroutine or another suspend function.

How to launch corutines in spring applications

I have an app that consumes some messages from a message Queue and processes them. The processing is implemented as suspending functions and there is a service that will publish the events to a Channel<Event>, I have another service that will basically do:
for (event in channel) {
eventProcessor.process(event)
}
The problem is that this is also a suspending function, and I am really not sure what's the proper way to launch it within the context of Spring.
My initial solution was to do the following:
#Bean
fun myProcessor(eventProcessor: EventProcessor, channel: Channel<Event>): Job {
GlobalScope.launch {
eventProcessor.startProcessing(channel)
}
}
But it seems somehow hacky, and I am not sure what's the proper way to do it.
Launching anything on a GlobalScope is a really bad idea. You loose all advantages of structured concurrency this way.
Instead, make your EventProcessor implement CoroutineScope.
This will force you to specify coroutineContext, so you can use Dispatchers.Default:
override val coroutineContext = Dispatchers.Default
So, the full example will look something like this:
#SpringBootApplication
class SpringKotlinCoroutinesApplication {
#Bean
fun myProcessor(eventProcessor: EventProcessor, channel: Channel<Event>): Job {
return eventProcessor.startProcessing(channel)
}
#Bean
fun p() = EventProcessor()
#Bean
fun c() = Channel<Event>()
}
fun main(args: Array<String>) {
runApplication<SpringKotlinCoroutinesApplication>(*args)
}
class Event
class EventProcessor : CoroutineScope {
override val coroutineContext = Dispatchers.Default
fun startProcessing(channel: Channel<Event>) = launch {
for (e in channel) {
println(e)
}
}
}

What is the most likely cause of exceptions mysteriously escaping a try-catch block in this case?

I am using a Spring WebClient in a Kotlin project like this:
data class DTO(val name: String)
#Component
class Runner: ApplicationRunner
{
override fun run(args: ApplicationArguments?)
{
try
{
val dto = get<DTO>()
}
catch (e: Exception)
{
println("ERROR, all exceptions should have been caught in 'get' ")
}
}
}
inline private fun<reified TResult: Any> get(): TResult?
{
var result: TResult? = null
try
{
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
}
catch (e: Exception)
{
println("WORKS AS EXPECTED!!")
}
return result
}
The client will throw an exception, because the API will return a 404. However the exception is not caught where it should be, namely in the body of the get function, but it is propagated to the outer exception handler.
It is interesting to note that this happens only if the exception is thrown by the WebClient. If I replace the code in the try clause with a simple throw Exception("error"), the exception is caught where it should be.
Similarly, when I change the signature of get to a non-generic inline private fun get(): DTO? the problem also goes away.
For an exception to escape the try-catch block seems like a fundamental bug in the Kotlin tools. On the other hand, the fact that this happens only with the WebClient class indicates that this is a Spring problem. Or, it may be just me, using the tools in a wrong way.
I am really baffled here and have no idea how to proceed. Any ideas on why this might be happening are most welcome. Just for completeness, this is what it looks like in the debugger:
EDIT
The issue goes away after upgrading Spring Boot to 2.0.0.M6, it is still present in M5.
So it seems that this was a Spring issue and not a Kotlin issue. On the other hand it would be still nice to understand how a library that you include can seemingly cause the program to violate the laws of the programming language it is written in.
I tried the code with Spring Boot version 2.0.0.M5 and 2.0.0.M6, and it seems the behavior of the following block is different between those 2 versions:
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
somewhere along the chain, on Spring Boot 2.0.0.M5, the WebClientResponseException is returned, on Spring Boot 2.0.0.M6 it is thrown.
If you add a e.printStackTrace() to your outer catch, you will notice that the stack trace is:
java.lang.ClassCastException:
org.springframework.web.reactive.function.client.WebClientResponseException
cannot be cast to com.example.demo.DTO at
com.example.demo.Runner.run(Test.kt:18) at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780)
at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:770)
at
org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:760)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:10)
So, actually, problem is, the returned WebClientResponseException is tried to be cast to DTO class on the moment of return of the call val dto = get<DTO>(). This means that, when you assign result = ..., there is no type checking done yet. So, if you change your code to, for example, call get<Object>() instead of get<DTO>(), it won't hit any catch blocks.
If you convert it to bytecode in IntelliJ Idea, and then decompile it to Java, you can see this block:
public class Runner implements ApplicationRunner {
public void run(#Nullable ApplicationArguments args) {
try {
Object result$iv = null;
try {
ResponseSpec $receiver$iv$iv = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting").get().retrieve();
Mono var10000 = $receiver$iv$iv.bodyToMono((ParameterizedTypeReference)(new Runner$run$$inlined$get$1()));
Intrinsics.checkExpressionValueIsNotNull(var10000, "bodyToMono(object : Para…zedTypeReference<T>() {})");
result$iv = var10000.block();
} catch (Exception var7) {
String var5 = "WORKS AS EXPECTED!!";
System.out.println(var5);
}
DTO var2 = (DTO)result$iv;
} catch (Exception var8) {
String var3 = "ERROR, all exceptions should have been caught in 'get' ";
System.out.println(var3);
}
}
}
Here you can notice that casting to DTO is done on the point of method return (which is not a return anymore because it is inlined), after the inner catch block: DTO var2 = (DTO)result$iv;. It seems like that's the behavior for the inlined methods with reified type parameters.
This is due to SPR-16025 (see related commit) since the Kotlin extension is using internally the ParameterizedTypeReference variant, which has been fixed in Spring Framework 5.0.1, and transitively in Spring Boot 2.0.0.M6.
Note than if you use bodyToMono(TResult::class.java) with Spring Boot 2.0.0.M5, it will works as expected.

Resources