Skip to content

Commit 47e3974

Browse files
committed
update multi-thread, coroutine env docs
1 parent 446f6e0 commit 47e3974

File tree

4 files changed

+128
-292
lines changed

4 files changed

+128
-292
lines changed

README-java-asynchronous-environments-EN.md

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ So, the new thread cannot access the original request context.
2727

2828
Spring provides `TaskDecorator` which allows copying the request context to new threads before executing asynchronous tasks.
2929

30-
### 1-1. Configure ThreadPoolTaskExecutor
30+
### 1. Configure ThreadPoolTaskExecutor
3131

3232
```java
3333
import org.springframework.context.annotation.Bean;
@@ -68,7 +68,7 @@ public class AsyncConfig {
6868
}
6969
```
7070

71-
### 1-2. Using CompletableFuture
71+
### 2. Using CompletableFuture
7272

7373
When using CompletableFuture directly, use the custom Executor:
7474

@@ -84,7 +84,7 @@ public CompletableFuture<List<User>> findAllUsersAsync() {
8484
}
8585
```
8686

87-
### 1-3. Using @Async Annotation
87+
### 3. Using @Async Annotation
8888

8989
When using the @Async annotation, explicitly specify the configured Executor:
9090

@@ -101,17 +101,4 @@ public CompletableFuture<User> findUserByIdAsync(Long id) {
101101

102102
## 🖥️ WebFlux/Reactor Environment
103103

104-
In a WebFlux environment, you can propagate the context as follows:
105-
106-
```java
107-
@GetMapping("/users/reactive")
108-
public Flux<User> getAllUsers() {
109-
return Flux.deferContextual(contextView -> {
110-
// Get information from the current context
111-
// Execute async operations
112-
return userRepository.findAllReactive();
113-
});
114-
}
115-
```
116-
117-
You can also automate context propagation using library like `reactor-core-micrometer`.
104+
(WIP)

README-java-asynchronous-environments.md

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,4 @@ public CompletableFuture<User> findUserByIdAsync(Long id) {
102102

103103
## 🖥️ WebFlux/Reactor 환경
104104

105-
WebFlux 환경에서는 다음과 같이 컨텍스트를 전파할 수 있습니다:
106-
107-
```java
108-
@GetMapping("/users/reactive")
109-
public Flux<User> getAllUsers() {
110-
return Flux.deferContextual(contextView -> {
111-
// 현재 컨텍스트에서 정보 가져오기
112-
// 비동기 작업 실행하기
113-
return userRepository.findAllReactive();
114-
});
115-
}
116-
```
117-
118-
그 외에도 `reactor-core-micrometer` 와 같은 라이브러리를 활용하여 컨텍스트 전파를 자동화 할 수 있습니다.
105+
(WIP)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# 🖥️ Kotlin Coroutine Environments QueryCountPerRequest Usage Guide
2+
3+
**English** | [한국어](README-kotlin-coroutine-environments.md)
4+
5+
## 🖥️ Overview
6+
7+
The Multi-Datasource-Query-Counter library counts the number of DB queries per API request.
8+
The library basically uses `@RequestScope` to ensure that each API request has its own counter.
9+
However, in Kotlin Coroutine environments, since Coroutines can freely move between threads (`suspension`, `resumption`),
10+
an issue may occur where the context of `RequestContextHolder` is not maintained.
11+
12+
This guide explains how to properly use `QueryCountPerRequest` in Coroutine environments.
13+
14+
<br>
15+
16+
## 🖥️ The Problem
17+
18+
Coroutines have the following characteristics:
19+
20+
1. **Suspension, Resumption**: Coroutines can be suspended and resumed at any time.
21+
2. **Thread Switching**: When a Coroutine resumes, it can resume on a different thread than the original one.
22+
3. **Context Propagation**: When starting a new Coroutine with builders like `async` or `launch`, the parent Coroutine's context may not be automatically propagated.
23+
24+
Therefore, it is difficult to properly use `QueryCountPerRequest` by default.
25+
26+
<br>
27+
28+
## 🖥️ Solution: Coroutine Context Element
29+
30+
### 1. Using CoroutineQueryCountContextElement
31+
32+
`CoroutineQueryCountContextElement` implements the [`ThreadContextElement` interface](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/) to propagate `RequestAttributes` to the branching Coroutine contexts.
33+
34+
```kotlin
35+
class CoroutineQueryCountContextElement(
36+
private val requestAttributes: RequestAttributes = RequestContextHolder.currentRequestAttributes(),
37+
) : ThreadContextElement<RequestAttributes> {
38+
39+
companion object Key : CoroutineContext.Key<CoroutineQueryCountContextElement>
40+
41+
override val key: CoroutineContext.Key<CoroutineQueryCountContextElement>
42+
get() = Key
43+
44+
override fun updateThreadContext(context: CoroutineContext): RequestAttributes {
45+
RequestContextHolder.setRequestAttributes(requestAttributes)
46+
return requestAttributes
47+
}
48+
49+
override fun restoreThreadContext(context: CoroutineContext, oldState: RequestAttributes) {
50+
RequestContextHolder.setRequestAttributes(oldState)
51+
}
52+
}
53+
```
54+
55+
The actual implementation is included in the library, so you can use it immediately.
56+
57+
### 2. Usage Example
58+
59+
```kotlin
60+
@RestController
61+
class UserController(
62+
private val userRepository: UserRepository,
63+
) {
64+
@CountQueries
65+
@GetMapping("/users/coroutine")
66+
fun getUsers() {
67+
// Now this Coroutine and its branching Coroutines maintain the same RequestAttributes
68+
val element = CoroutineQueryCountContextElement()
69+
val threadPool = ForkJoinPool(2)
70+
return runBlocking(threadPool.asCoroutineDispatcher() + element) {
71+
val usersDeferred = async {
72+
userRepository.findAllUsers() // Query count point.
73+
}
74+
75+
val countDeferred = async {
76+
userRepository.count() // Query count point.
77+
}
78+
79+
val users = usersDeferred.await()
80+
val count = countDeferred.await()
81+
82+
mapOf("users" to users, "count" to count)
83+
}
84+
}
85+
}
86+
```
87+
88+
<br>
89+
90+
## 🖥️ WebFlux/Reactor Environment
91+
92+
(WIP)

0 commit comments

Comments
 (0)