Slide 1

Slide 1 text

Could Virtual Threads cast away the usage of Kotlin Coroutines? What experience can tell us… How coroutines have changed the JVM programming world and how Java Virtual Threads can change that? By João Esperancinha 2024/09/10 https://www.eventbrite.co.uk/e/ljc-meet-up-tickets-988279208717

Slide 2

Slide 2 text

Who am I? João is a 10+ years experienced Software Developer, Studied Telecom Engineering at ISEL Lisboa, and worked in different companies in Portugal, Spain, and The Netherlands. He is a certified Kong Champion, a certified Java Developer (OCP11) and is a Certified Spring Professional 2021. Outside the software development Universe, João is someone interested in arts and crafts and making stuff out of old materials creating the occasional modified furniture. João loves plants and has a small potted garden with Yuccas and blueberry bushes. He has one particular YouTube channel called JESPROTECH dedicated to engineering where João shares his experience with the world.

Slide 3

Slide 3 text

Quick Threads History 1995 - Java 1.0 and Green Threads 1998 - Java 1.2 and Transition to Native Threads 2004 - Java 5 (1.5) and Concurrency Utilities 2011 - Kotlin Language Announced 2014 - Project Loom Announced 2017 - Kotlin 1.1 and Introduction of Coroutines 2019 - Project Loom Early Access Builds 2022 - Java 19 and Preview of Virtual Threads 2023 - Java 21 and Official Release of Virtual Threads

Slide 4

Slide 4 text

Fetch all data diagram (Horrendous old way to fetch data for Server Side, but great for mobile … sometimes) Fetching data

Slide 5

Slide 5 text

Structured Concurrency using Virtual Threads try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { var userDeferred = scope.fork(() -> fetchUser(userId)); var postsDeferred = scope.fork(() -> fetchUserPosts(userId)); var commentsDeferred = scope.fork(() -> fetchUserComments(userId)); scope.join(); scope.throwIfFailed(); var processedData = processUserData( userDeferred.get(), postsDeferred.get(), commentsDeferred.get()); updateUI(processedData); logger.info(() -> "Complete!"); } catch (ExecutionException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } Fetching data with Java Virtual Threads (Java) Highly verbose! This is blocking! Alternative: Thread.startVirtualThread ?

Slide 6

Slide 6 text

Structured Concurrency using Virtual Threads ShutdownOnFailure().use { scope -> val userDeferred = scope.fork { fetchUser(userId) } val postsDeferred = scope.fork { fetchUserPosts(userId) } val commentsDeferred = scope.fork { fetchUserComments( userId ) } scope.join() scope.throwIfFailed() val processedData = processUserData( userDeferred.get(), postsDeferred.get(), commentsDeferred.get() ) updateSytem(processedData) logger.info("Complete!") } Fetching data with Java Virtual Threads (Kotlin) Still blocking! Alternative: Thread.startVirtualThread ? Still Highly verbose!

Slide 7

Slide 7 text

Structured Concurrency using Kotlin Coroutines CoroutineScope(IO).launch { val userDeferred = async { fetchUser(userId) } val postsDeferred = async { fetchUserPosts(userId) } val commentsDeferred = async { fetchUserComments(userId) } val user = userDeferred.await() val posts = postsDeferred.await() val comments = commentsDeferred.await() val processedData = processUserData(user, posts, comments) updateSytem(processedData) logger.info("Complete!") } Fetching data Is this not verbose? 🤔 It is better! 😀 Not blocking! 😀

Slide 8

Slide 8 text

Sending messages Send messages diagram (N user messages, packed in 50 messages sent in parallel - Fire and forget)

Slide 9

Slide 9 text

Fire and Forget with Kotlin Coroutines suspend fun sendMessage(text: String, users: List) = coroutineScope { users.chunked(50).map { it.map { async { retry(5, 500) { sendEmail(text, it) } } }.awaitAll() } } Sending Messages Needs a coroutine scope!

Slide 10

Slide 10 text

Fire and Forget with Java Virtual Threads fun sendMessage(text: String, users: List) = users.chunked(50).map { it.map { startVirtualThread { retry(5, 500) { sendEmail(text, it) } } }.forEach { it.join() } } Sending Messages No need for a coroutine scope… but now we wait?🤔 Now it is just a choice! 😀 Example: Thread.startVirtualThread

Slide 11

Slide 11 text

Making tests Using docker-compose 1. services: 2. fulfilment: 3. build: 4. context: . 5. ports: 6. - "8080:8080" 7. expose: 8. - 8080 9. deploy: 10. resources: 11. limits: 12. cpus: 0.5 13. memory: 270M locust -f locust_fulfilment.py --host=http://localhost:8080 Take a part of the machine

Slide 12

Slide 12 text

Python code to make tests Python code called by locust to perform tests from locust import HttpUser, TaskSet, task, between class UserBehavior(TaskSet): @task def get_fulfilment(self): url = "/api/v1/fulfilment" params = {} headers = {"Accept": "application/json"} with self.client.get(url, params=params, headers=headers, catch_response=True) as response: if response.status_code == 200: response.success() print(f"Status Code: {response.status_code}, Response: {response.text[:100]}") else: response.failure(f"Failed with status code: {response.status_code}") class WebsiteUser(HttpUser): tasks = [UserBehavior] wait_time = between(1, 3) def on_start(self): pass def on_stop(self): pass URL + Headers

Slide 13

Slide 13 text

Traditional Spring MVC @RestController @RequestMapping("fulfilment") class FulfilmentController { @GetMapping fun getItems() = (1..10).map { val product = Product(name = "TV", isleType = Room) logger.info("Product: $product") product.name } companion object { val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java) } } This was the old way. Or is it? 🤔

Slide 14

Slide 14 text

Locust test - Max 100000 users ; Spawn Rate 1000 users/second (~20 seconds = 20000+ simultaneously - MVC)

Slide 15

Slide 15 text

Traditional Spring MVC Great pattern, but not that capable. Not “fast” enough. ● Easy pattern to learn ● Good 3 tier model ● Model, View, Controller ● Domain model ● Data transfer object ● Converters Problem? ● Not many were aware of the inefficient aspect of it ● The inefficiency only appeared when raising the load Why was there a need to improve it? ● The idea was there all along ● When comparing the performance of Spring for higher loads, Spring and other JVM based solutions would fail to be on top. June 2003

Slide 16

Slide 16 text

Reactive Services in Spring MVC - WebFlux @RestController @RequestMapping("fulfilment") class FulfilmentController { @GetMapping fun getItems(): Flux = Flux.fromIterable (1..10).map { val product = Product(name = "TV", isleType = Room) logger.info("Product: $product") product.name } companion object { val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java) } } Flux starts reactive revolution, but how good was/is it?

Slide 17

Slide 17 text

Locust test - Max 100000 users ; Spawn Rate 1000 users/second (27 seconds = 27000 + simultaneously - WebFlux)

Slide 18

Slide 18 text

Reactive Programming ● Blockhound ● Flux / Mono ● Flux were used for collections ● Mono used for instances ● Good support for R2DBC ● Good support for Spring ● The Observability pattern and publishers were visible Problem? ● Coding would get extremely complicated ● Particularly zipping functions Spring WebFlux - 2017 (my sea-shell-archiver became too complicated!)_

Slide 19

Slide 19 text

Reactive Services with Spring Kotlin Coroutines @RestController @RequestMapping("fulfilment") class FulfilmentController { @GetMapping fun getItems() = flowOf(1..10).map { val product = Product(name = "TV", isleType = Room) logger.info("Product: $product") product.name } companion object { val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java) } } A flow is programmed like flux but the backbone is made with Kotlin Coroutines and it works with the assigned dispatcher by Spring

Slide 20

Slide 20 text

Locust test - Max 100000 users ; Spawn Rate 1000 users/second

Slide 21

Slide 21 text

Why Kotlin Coroutines? Kotlin’s inception year - 2016 ● Scope creation instead of a specific structured concurrency block ● Structured concurrency ● Non-preemptive scheduling ● Cooperative multitasking ● Better usage of resources ● Concurrency ● Additional benefit of parallelism ● Dispatchers

Slide 22

Slide 22 text

Kotlin Coroutines ● Claimed to be simpler than using Flux and Mono ● Instead of Mono we started using suspend ● Instead collections, we started using flow ● The rise of the usage of Channels, hot flows, cold flows ● Talks about removing Kafka, RabbitMQ and major dedicated data stream frameworks Problem? ● Coroutine complexity was still considerable when using structured concurrency ● Because of the non-preemptive model all functions and its instances would have to be marked as suspend ● No simple function could call suspend ● Many developers still try to use suspend with a returned list or even with flows. In some cases also with Flux. Compatibility with Spring - 2019

Slide 23

Slide 23 text

Coroutines to Virtual Threads (gross simplification)

Slide 24

Slide 24 text

Virtual Threads in Spring # Server server.port=8080 spring.main.web-application-type=servlet spring.mvc.servlet.path=/api/v1 # Enable Virtual Threads for WebFlux spring.threads.virtual.enabled=true # Thread pool configuration #spring.task.execution.pool.max-size=16 #spring.task.execution.pool.queue-capacity=100 #spring.task.execution.pool.keep-alive=10s We can activate virtual threads

Slide 25

Slide 25 text

Virtual Threads in Spring @RestController @RequestMapping("fulfilment") class FulfilmentController { @GetMapping fun getItems() = (1..10).map { val product = Product(name = "TV", isleType = Room) logger.info("Product: $product") product.name } companion object { val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java) } } It’s back! And now we are back to the old model!😁

Slide 26

Slide 26 text

Locust test - Max 100000 users ; Spawn Rate 1000 users/second

Slide 27

Slide 27 text

Virtual Threads ● Turns everything on its head for Spring ● There is no keyword or special objects needed ● The application stays reactive ● The preemptive/non-preemptive (hybrid) scheduling form makes sure that we can virtualize threads in a code that goes back to the imperative form, without the need to be forced to make major decisions ● The use of scopes is off the table and not needed Problem? ● Structured concurrency support is very verbose ● Many frameworks surrounding Kotlin like Project arrow rely on coroutines for their features. ● Streams, channels and flows offer full coroutine support, while it is still cumbersome to do that in Java ● Creating mobile applications, namely with android, uses a lot of coroutines Started with Loom as Fibers in 2017 Released in 2023 with the JDK 21

Slide 28

Slide 28 text

Coroutines with Virtual Threads (gross simplification)

Slide 29

Slide 29 text

Coroutines on Virtual Threads in Spring @RestController @RequestMapping("fulfilment") class FulfilmentController { @GetMapping fun getItems(): Flow = (1..10).asFlow().map { val product = Product(name = "TV", isleType = Room) logger.info("Product: $product") product.name }.flowOn( Executors.newVirtualThreadPerTaskExecutor() .asCoroutineDispatcher() ) companion object { val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java) } } An odd construction, but does it stand?🤔

Slide 30

Slide 30 text

Locust test - Max 100000 users ; Spawn Rate 1000 users/second

Slide 31

Slide 31 text

Virtual Threads Combined With Kotlin Coroutines ● A different perspective on Threads with potential benefits ● I never used it in production ● Maybe it is not an improvement. ● Again, testing is needed and lot of benchmarking is needed in order to make adequate decisions 2023 lot’s of blogs released: I did that too: https://github.com/jesperancinha /good-story

Slide 32

Slide 32 text

GitHub - Good Story - Virtual Thread Dispatcher in combination with Kotlin Coroutines

Slide 33

Slide 33 text

Conclusion ● Virtual threads are very impactful because of their support by the Spring Framework. They are easy to use and build code with. Maintain that code is easy. It can be used in: ○ Fire and forget scenarios ○ Structured concurrency ○ Both Java and Kotlin ● Coroutines will still be used because of how well and elegantly they adapt to other paradigms: ○ Flows ○ Channels ○ Event Streaming ○ SSE ○ Can be used in Both Java and Kotlin, but they are not prepared for Java and can get highly complicated that way ● In any case, both can be used with similar or purely equal performance markers, leading to choosing them on the bases of code style, patterns and design. Virtual Theads and Coroutines have a great future still

Slide 34

Slide 34 text

What next? ➔ Java Virtual Threads have a definite future, but for Android at the moment, only up to JDK17 is supported and no support is available for Java Virtual Threads as a result. ➔ Kotlin Coroutines will stick around for Android for a long time. ➔ In Java Server Side the use of Kotlin Coroutines may go down for simple applications and only where certain specific operations are needed will Kotlin coroutines still likely be needed due to their simplicity. ➔ For all operations related to structured concurrency, streams and event handling, the usage of Kotlin Coroutines may actually increase as is the case with Mobile Applications in Android. ➔ Maybe coroutines can one day not be used anymore given the potential of Java Virtual Threads even in the case of Android applications, but that isn’t anything for the near future.

Slide 35

Slide 35 text

Questions? I am an inquisitive cat

Slide 36

Slide 36 text

Resources Online ● https://kotlinlang.org/docs/coroutines-basics.html ● https://openjdk.org/jeps/444 ● https://openjdk.org/projects/jdk/21/ ● https://www.jetbrains.com/help/idea/supported-java-versions.html ● https://discuss.kotlinlang.org/t/coroutines-java-virtual-threads-and-scoped-values/28004/1 ● https://developer.android.com/build/jdks Slides available: ● https://www.scribd.com/presentation/768072685/Could-Virtual-Threads-Cast-Away-the-Usage- of-Kotlin-Coroutines ● https://www.slideshare.net/slideshow/could-virtual-threads-cast-away-the-usage-of-kotlin-coro utines/271727680

Slide 37

Slide 37 text

Source code and documentation ● https://github.com/jesperancinha/virtual-thread-coroutine-cooperation (Source code) ● https://github.com/jesperancinha/good-story (CPU bound operations comparison) ● https://github.com/jesperancinha/sea-shell-archiver (Coding with WebFlux - Mono/Flux/Zip) ● https://github.com/jesperancinha/concert-demos-root (Back Pressure testing in Spring WebFlux and R2DBC) ● https://github.com/jesperancinha/kitten-house-care-parent (Basics on resilient services)

Slide 38

Slide 38 text

About me ● Homepage - https://joaofilipesabinoesperancinha.nl ● LinkedIn - https://www.linkedin.com/in/joaoesperancinha/ ● YouTube - JESPROTECH ■ https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g ■ https://www.youtube.com/@jesprotech ● Bluesky - https://bsky.app/profile/jesperancinha.bsky.social ● Mastodon - https://masto.ai/@jesperancinha ● GitHub - https://github.com/jesperancinha ● Hackernoon - https://hackernoon.com/u/jesperancinha ● DevTO - https://dev.to/jofisaes ● Medium - https://medium.com/@jofisaes

Slide 39

Slide 39 text

No content