@kood/claude-code 0.6.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +7 -1
- package/package.json +1 -1
- package/templates/.claude/agents/analyst.md +5 -0
- package/templates/.claude/agents/architect.md +5 -0
- package/templates/.claude/agents/build-fixer.md +1 -0
- package/templates/.claude/agents/code-reviewer.md +1 -0
- package/templates/.claude/agents/critic.md +4 -0
- package/templates/.claude/agents/deep-executor.md +1 -0
- package/templates/.claude/agents/dependency-manager.md +2 -0
- package/templates/.claude/agents/deployment-validator.md +2 -0
- package/templates/.claude/agents/designer.md +2 -0
- package/templates/.claude/agents/document-writer.md +3 -0
- package/templates/.claude/agents/explore.md +1 -0
- package/templates/.claude/agents/git-operator.md +2 -0
- package/templates/.claude/agents/implementation-executor.md +2 -0
- package/templates/.claude/agents/ko-to-en-translator.md +3 -0
- package/templates/.claude/agents/lint-fixer.md +2 -0
- package/templates/.claude/agents/planner.md +3 -0
- package/templates/.claude/agents/pm.md +349 -0
- package/templates/.claude/agents/qa-tester.md +1 -0
- package/templates/.claude/agents/refactor-advisor.md +4 -0
- package/templates/.claude/agents/researcher.md +9 -1
- package/templates/.claude/agents/scientist.md +1 -0
- package/templates/.claude/agents/security-reviewer.md +1 -0
- package/templates/.claude/agents/tdd-guide.md +1 -0
- package/templates/.claude/agents/vision.md +1 -0
- package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
- package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
- package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
- package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
- package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
- package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
- package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
- package/templates/.claude/skills/crawler/SKILL.md +2 -0
- package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
- package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
- package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
- package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
- package/templates/.claude/skills/execute/SKILL.md +1 -0
- package/templates/.claude/skills/feedback/SKILL.md +1 -0
- package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
- package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
- package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
- package/templates/.claude/skills/plan/SKILL.md +1 -0
- package/templates/.claude/skills/prd/SKILL.md +1 -0
- package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
- package/templates/.claude/skills/project-optimizer/SKILL.md +375 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
- package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
- package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
- package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
- package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
- package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
- package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
- package/templates/.claude/skills/ralph/SKILL.md +1 -0
- package/templates/.claude/skills/refactor/SKILL.md +1 -0
- package/templates/.claude/skills/research/SKILL.md +1 -0
- package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
- package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
- package/templates/.claude/skills/startup-validator/SKILL.md +1 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
- package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +94 -27
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
- package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
- package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +571 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
- package/templates/tanstack-start/docs/architecture.md +238 -167
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
- package/templates/tauri/CLAUDE.md +189 -0
- package/templates/tauri/docs/guides/distribution.md +261 -0
- package/templates/tauri/docs/guides/getting-started.md +302 -0
- package/templates/tauri/docs/guides/mobile.md +288 -0
- package/templates/tauri/docs/library/tauri/index.md +510 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Async Mutex for IO Resources
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents blocking tokio runtime
|
|
5
|
+
tags: state, mutex, async, tokio, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# IO 리소스만 Tokio Mutex, 나머지는 Std Mutex
|
|
9
|
+
|
|
10
|
+
## 왜 중요한가
|
|
11
|
+
|
|
12
|
+
Rust의 `std::sync::Mutex`는 **CPU 작업**에 최적화되어 있고, `tokio::sync::Mutex`는 **비동기 IO 작업**에 최적화되어 있습니다. 잘못된 Mutex를 사용하면 **성능 저하**나 **데드락**이 발생할 수 있습니다.
|
|
13
|
+
|
|
14
|
+
## ❌ 잘못된 패턴
|
|
15
|
+
|
|
16
|
+
**모든 곳에 Tokio Mutex 사용 (CPU 작업에서 오버헤드):**
|
|
17
|
+
|
|
18
|
+
```rust
|
|
19
|
+
// ❌ src-tauri/src/main.rs
|
|
20
|
+
use tokio::sync::Mutex; // ❌ CPU 작업에는 비효율적
|
|
21
|
+
use tauri::State;
|
|
22
|
+
|
|
23
|
+
struct AppStateInner {
|
|
24
|
+
counter: i32, // CPU 작업
|
|
25
|
+
database: Database // IO 작업
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type AppState = Mutex<AppStateInner>;
|
|
29
|
+
|
|
30
|
+
#[tauri::command]
|
|
31
|
+
async fn increment(state: State<'_, AppState>) -> Result<i32, String> {
|
|
32
|
+
let mut state = state.lock().await; // ❌ 불필요한 async
|
|
33
|
+
state.counter += 1; // CPU 작업인데 tokio::Mutex 사용
|
|
34
|
+
Ok(state.counter)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[tauri::command]
|
|
38
|
+
async fn query_db(state: State<'_, AppState>) -> Result<String, String> {
|
|
39
|
+
let state = state.lock().await; // ❌ Database도 함께 락
|
|
40
|
+
let result = state.database.query("SELECT * FROM users").await?;
|
|
41
|
+
Ok(result)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**문제:**
|
|
46
|
+
- `increment`는 CPU 작업인데 `tokio::Mutex`의 async 오버헤드 발생
|
|
47
|
+
- `database` IO 작업 중에도 `counter`가 락되어 병렬성 저하
|
|
48
|
+
|
|
49
|
+
## ✅ 올바른 패턴
|
|
50
|
+
|
|
51
|
+
**CPU 작업과 IO 작업을 분리, 적절한 Mutex 사용:**
|
|
52
|
+
|
|
53
|
+
```rust
|
|
54
|
+
// ✅ src-tauri/src/state.rs
|
|
55
|
+
use std::sync::Mutex as StdMutex;
|
|
56
|
+
use tokio::sync::Mutex as TokioMutex;
|
|
57
|
+
|
|
58
|
+
pub struct Database {
|
|
59
|
+
pool: sqlx::Pool<sqlx::Postgres>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl Database {
|
|
63
|
+
pub async fn query(&self, sql: &str) -> Result<String, sqlx::Error> {
|
|
64
|
+
let rows = sqlx::query(sql)
|
|
65
|
+
.fetch_all(&self.pool)
|
|
66
|
+
.await?;
|
|
67
|
+
Ok(format!("{:?}", rows))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub struct AppStateInner {
|
|
72
|
+
// CPU 작업: std::sync::Mutex
|
|
73
|
+
counter: StdMutex<i32>,
|
|
74
|
+
settings: StdMutex<AppSettings>,
|
|
75
|
+
|
|
76
|
+
// IO 작업: tokio::sync::Mutex
|
|
77
|
+
database: TokioMutex<Database>,
|
|
78
|
+
http_client: TokioMutex<reqwest::Client>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub type AppState = AppStateInner;
|
|
82
|
+
|
|
83
|
+
impl AppStateInner {
|
|
84
|
+
pub fn new(db_pool: sqlx::Pool<sqlx::Postgres>) -> Self {
|
|
85
|
+
Self {
|
|
86
|
+
counter: StdMutex::new(0),
|
|
87
|
+
settings: StdMutex::new(AppSettings::default()),
|
|
88
|
+
database: TokioMutex::new(Database { pool: db_pool }),
|
|
89
|
+
http_client: TokioMutex::new(reqwest::Client::new())
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```rust
|
|
96
|
+
// ✅ src-tauri/src/main.rs
|
|
97
|
+
mod state;
|
|
98
|
+
|
|
99
|
+
use state::{AppState, AppStateInner};
|
|
100
|
+
use tauri::State;
|
|
101
|
+
|
|
102
|
+
// CPU 작업: 동기 함수, std::Mutex
|
|
103
|
+
#[tauri::command]
|
|
104
|
+
fn increment(state: State<'_, AppState>) -> Result<i32, String> {
|
|
105
|
+
let mut counter = state.counter.lock()
|
|
106
|
+
.map_err(|e| format!("Failed to lock counter: {}", e))?;
|
|
107
|
+
*counter += 1;
|
|
108
|
+
Ok(*counter)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// IO 작업: async 함수, tokio::Mutex
|
|
112
|
+
#[tauri::command]
|
|
113
|
+
async fn query_db(state: State<'_, AppState>) -> Result<String, String> {
|
|
114
|
+
let db = state.database.lock().await; // await 필요
|
|
115
|
+
let result = db.query("SELECT * FROM users")
|
|
116
|
+
.await
|
|
117
|
+
.map_err(|e| e.to_string())?;
|
|
118
|
+
Ok(result)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// IO 작업: async 함수, tokio::Mutex
|
|
122
|
+
#[tauri::command]
|
|
123
|
+
async fn fetch_data(state: State<'_, AppState>, url: String) -> Result<String, String> {
|
|
124
|
+
let client = state.http_client.lock().await;
|
|
125
|
+
let response = client.get(&url)
|
|
126
|
+
.send()
|
|
127
|
+
.await
|
|
128
|
+
.map_err(|e| e.to_string())?
|
|
129
|
+
.text()
|
|
130
|
+
.await
|
|
131
|
+
.map_err(|e| e.to_string())?;
|
|
132
|
+
Ok(response)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[tokio::main]
|
|
136
|
+
async fn main() {
|
|
137
|
+
let db_pool = sqlx::postgres::PgPoolOptions::new()
|
|
138
|
+
.connect("postgres://localhost/mydb")
|
|
139
|
+
.await
|
|
140
|
+
.expect("Failed to connect to database");
|
|
141
|
+
|
|
142
|
+
tauri::Builder::default()
|
|
143
|
+
.manage(AppStateInner::new(db_pool))
|
|
144
|
+
.invoke_handler(tauri::generate_handler![
|
|
145
|
+
increment,
|
|
146
|
+
query_db,
|
|
147
|
+
fetch_data
|
|
148
|
+
])
|
|
149
|
+
.run(tauri::generate_context!())
|
|
150
|
+
.expect("error while running tauri application");
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**RwLock 혼합 사용 (읽기 많은 경우):**
|
|
155
|
+
|
|
156
|
+
```rust
|
|
157
|
+
use std::sync::RwLock as StdRwLock;
|
|
158
|
+
use tokio::sync::RwLock as TokioRwLock;
|
|
159
|
+
|
|
160
|
+
pub struct AppStateInner {
|
|
161
|
+
// 읽기 많은 CPU 작업: std::sync::RwLock
|
|
162
|
+
config: StdRwLock<AppConfig>,
|
|
163
|
+
|
|
164
|
+
// 읽기 많은 IO 작업: tokio::sync::RwLock
|
|
165
|
+
cache: TokioRwLock<Cache>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[tauri::command]
|
|
169
|
+
fn get_config(state: State<'_, AppState>) -> Result<String, String> {
|
|
170
|
+
let config = state.config.read()
|
|
171
|
+
.map_err(|e| format!("Failed to read config: {}", e))?;
|
|
172
|
+
Ok(format!("{:?}", *config))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[tauri::command]
|
|
176
|
+
async fn get_cached_data(state: State<'_, AppState>, key: String) -> Result<String, String> {
|
|
177
|
+
let cache = state.cache.read().await;
|
|
178
|
+
cache.get(&key)
|
|
179
|
+
.await
|
|
180
|
+
.ok_or_else(|| "Key not found".to_string())
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 추가 컨텍스트
|
|
185
|
+
|
|
186
|
+
**Mutex 선택 가이드:**
|
|
187
|
+
|
|
188
|
+
| 작업 유형 | Mutex 종류 | 이유 |
|
|
189
|
+
|-----------|-----------|------|
|
|
190
|
+
| CPU 계산 (counter, settings) | `std::sync::Mutex` | OS 레벨 락, 오버헤드 낮음 |
|
|
191
|
+
| IO 작업 (DB, HTTP, 파일) | `tokio::sync::Mutex` | async/await 호환, tokio runtime 차단 안함 |
|
|
192
|
+
| 읽기 많음 + CPU | `std::sync::RwLock` | 여러 스레드 동시 읽기 |
|
|
193
|
+
| 읽기 많음 + IO | `tokio::sync::RwLock` | 비동기 읽기 병렬화 |
|
|
194
|
+
|
|
195
|
+
**성능 비교:**
|
|
196
|
+
|
|
197
|
+
```rust
|
|
198
|
+
// 벤치마크 예시 (단순 증가 연산 1,000,000회)
|
|
199
|
+
// std::sync::Mutex: 50ms
|
|
200
|
+
// tokio::sync::Mutex: 120ms (2.4배 느림)
|
|
201
|
+
|
|
202
|
+
// IO 작업 시 (100ms 네트워크 요청)
|
|
203
|
+
// std::sync::Mutex: tokio runtime 차단 → 다른 작업 대기
|
|
204
|
+
// tokio::sync::Mutex: 다른 작업 병렬 실행 가능
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**데드락 위험:**
|
|
208
|
+
|
|
209
|
+
```rust
|
|
210
|
+
// ❌ std::sync::Mutex를 async 블록 안에서 .await 넘어 사용
|
|
211
|
+
async fn bad_example(state: State<'_, AppState>) {
|
|
212
|
+
let lock = state.counter.lock().unwrap(); // std::Mutex
|
|
213
|
+
some_async_function().await; // ❌ .await 동안 락 유지 → 데드락 위험
|
|
214
|
+
println!("{}", *lock);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ✅ 락 범위 최소화
|
|
218
|
+
async fn good_example(state: State<'_, AppState>) {
|
|
219
|
+
let value = {
|
|
220
|
+
let lock = state.counter.lock().unwrap();
|
|
221
|
+
*lock // 값 복사 후 락 해제
|
|
222
|
+
};
|
|
223
|
+
some_async_function().await; // ✅ 락 해제 후 await
|
|
224
|
+
println!("{}", value);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Tauri 2.0+ 권장 패턴:**
|
|
229
|
+
|
|
230
|
+
```rust
|
|
231
|
+
// Tauri 2.0부터는 async command가 기본
|
|
232
|
+
// IO 작업은 tokio::Mutex + async
|
|
233
|
+
// CPU 작업은 std::Mutex + 동기 함수 (Tauri가 tokio::spawn_blocking 자동 처리)
|
|
234
|
+
|
|
235
|
+
#[tauri::command]
|
|
236
|
+
fn cpu_intensive_task(state: State<'_, AppState>) -> Result<i32, String> {
|
|
237
|
+
// Tauri가 자동으로 blocking thread에서 실행
|
|
238
|
+
let mut data = state.cpu_data.lock().unwrap();
|
|
239
|
+
// 무거운 CPU 작업...
|
|
240
|
+
Ok(*data)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Arc는 언제 사용?**
|
|
245
|
+
|
|
246
|
+
```rust
|
|
247
|
+
// ❌ Tauri State에서는 Arc 불필요
|
|
248
|
+
use std::sync::Arc;
|
|
249
|
+
type AppState = Arc<Mutex<AppStateInner>>; // ❌ 불필요한 Arc
|
|
250
|
+
|
|
251
|
+
// ✅ Tauri가 자동으로 State를 관리 (이미 Arc 내부 사용)
|
|
252
|
+
type AppState = Mutex<AppStateInner>; // ✅ 충분
|
|
253
|
+
|
|
254
|
+
// ✅ State 외부에서 공유 필요 시에만 Arc 사용
|
|
255
|
+
let shared_db = Arc::new(Database::new());
|
|
256
|
+
let db_clone = Arc::clone(&shared_db);
|
|
257
|
+
tokio::spawn(async move {
|
|
258
|
+
db_clone.query("...").await;
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**참고:**
|
|
263
|
+
- Tokio Mutex: [tokio::sync::Mutex](https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html)
|
|
264
|
+
- Std Mutex: [std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
|
265
|
+
- Tauri Async: [Async Commands](https://tauri.app/v2/guides/features/commands/#async-commands)
|
|
266
|
+
|
|
267
|
+
**영향도:**
|
|
268
|
+
- 성능: MEDIUM (CPU 작업 2-3배, IO 작업 병렬성 향상)
|
|
269
|
+
- 데드락 방지: HIGH (올바른 Mutex 선택)
|
|
270
|
+
- 복잡도: MEDIUM (작업 유형 판단 필요)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mutex for Mutable State
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents panic from concurrent access
|
|
5
|
+
tags: state, mutex, thread-safety, tauri, rust
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mutex로 가변 상태 관리, 타입 별칭 사용
|
|
9
|
+
|
|
10
|
+
## 왜 중요한가
|
|
11
|
+
|
|
12
|
+
Tauri의 상태 관리는 **여러 스레드에서 동시에 접근**할 수 있습니다. 가변 상태를 `Mutex`로 감싸지 않으면 **런타임 패닉**이 발생하며, 타입 불일치로 인한 버그를 방지하기 위해 **타입 별칭**을 사용해야 합니다.
|
|
13
|
+
|
|
14
|
+
## ❌ 잘못된 패턴
|
|
15
|
+
|
|
16
|
+
**Mutex 없이 가변 상태 관리 (패닉 발생):**
|
|
17
|
+
|
|
18
|
+
```rust
|
|
19
|
+
// ❌ src-tauri/src/main.rs
|
|
20
|
+
use tauri::State;
|
|
21
|
+
|
|
22
|
+
struct AppState {
|
|
23
|
+
count: i32
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[tauri::command]
|
|
27
|
+
fn increment(state: State<'_, AppState>) -> i32 {
|
|
28
|
+
state.count += 1; // ❌ 컴파일 에러: cannot mutate immutable field
|
|
29
|
+
state.count
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn main() {
|
|
33
|
+
tauri::Builder::default()
|
|
34
|
+
.manage(AppState { count: 0 })
|
|
35
|
+
.invoke_handler(tauri::generate_handler![increment])
|
|
36
|
+
.run(tauri::generate_context!())
|
|
37
|
+
.expect("error while running tauri application");
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Mutex 사용하지만 타입 별칭 없음 (불일치 위험):**
|
|
42
|
+
|
|
43
|
+
```rust
|
|
44
|
+
// ⚠️ src-tauri/src/main.rs
|
|
45
|
+
use std::sync::Mutex;
|
|
46
|
+
use tauri::State;
|
|
47
|
+
|
|
48
|
+
struct AppStateInner {
|
|
49
|
+
count: i32
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[tauri::command]
|
|
53
|
+
fn increment(state: State<'_, Mutex<AppStateInner>>) -> i32 {
|
|
54
|
+
let mut state = state.lock().unwrap();
|
|
55
|
+
state.count += 1;
|
|
56
|
+
state.count
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[tauri::command]
|
|
60
|
+
fn get_count(state: State<'_, AppStateInner>) -> i32 {
|
|
61
|
+
// ❌ 타입 불일치: Mutex<AppStateInner>를 등록했지만
|
|
62
|
+
// AppStateInner로 접근 시도 → 런타임 패닉!
|
|
63
|
+
state.count
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn main() {
|
|
67
|
+
tauri::Builder::default()
|
|
68
|
+
.manage(Mutex::new(AppStateInner { count: 0 }))
|
|
69
|
+
.invoke_handler(tauri::generate_handler![increment, get_count])
|
|
70
|
+
.run(tauri::generate_context!())
|
|
71
|
+
.expect("error while running tauri application");
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**런타임 에러:**
|
|
76
|
+
```
|
|
77
|
+
thread 'main' panicked at 'Failed to get state: no state of type `AppStateInner` is managed'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## ✅ 올바른 패턴
|
|
81
|
+
|
|
82
|
+
**Mutex + 타입 별칭으로 타입 안전성 보장:**
|
|
83
|
+
|
|
84
|
+
```rust
|
|
85
|
+
// ✅ src-tauri/src/main.rs
|
|
86
|
+
use std::sync::Mutex;
|
|
87
|
+
use tauri::State;
|
|
88
|
+
|
|
89
|
+
// 내부 상태 구조체
|
|
90
|
+
struct AppStateInner {
|
|
91
|
+
count: i32,
|
|
92
|
+
username: String
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 타입 별칭으로 Mutex 포함
|
|
96
|
+
type AppState = Mutex<AppStateInner>;
|
|
97
|
+
|
|
98
|
+
#[tauri::command]
|
|
99
|
+
fn increment(state: State<'_, AppState>) -> i32 {
|
|
100
|
+
let mut state = state.lock().unwrap();
|
|
101
|
+
state.count += 1;
|
|
102
|
+
state.count
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[tauri::command]
|
|
106
|
+
fn get_count(state: State<'_, AppState>) -> i32 {
|
|
107
|
+
let state = state.lock().unwrap();
|
|
108
|
+
state.count
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[tauri::command]
|
|
112
|
+
fn set_username(state: State<'_, AppState>, name: String) {
|
|
113
|
+
let mut state = state.lock().unwrap();
|
|
114
|
+
state.username = name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn main() {
|
|
118
|
+
tauri::Builder::default()
|
|
119
|
+
.manage(AppState::new(AppStateInner {
|
|
120
|
+
count: 0,
|
|
121
|
+
username: String::from("Guest")
|
|
122
|
+
}))
|
|
123
|
+
.invoke_handler(tauri::generate_handler![
|
|
124
|
+
increment,
|
|
125
|
+
get_count,
|
|
126
|
+
set_username
|
|
127
|
+
])
|
|
128
|
+
.run(tauri::generate_context!())
|
|
129
|
+
.expect("error while running tauri application");
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**별도 파일로 상태 관리 분리:**
|
|
134
|
+
|
|
135
|
+
```rust
|
|
136
|
+
// ✅ src-tauri/src/state.rs
|
|
137
|
+
use std::sync::Mutex;
|
|
138
|
+
use serde::{Deserialize, Serialize};
|
|
139
|
+
|
|
140
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
141
|
+
pub struct AppStateInner {
|
|
142
|
+
pub count: i32,
|
|
143
|
+
pub username: String,
|
|
144
|
+
pub is_logged_in: bool
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
impl Default for AppStateInner {
|
|
148
|
+
fn default() -> Self {
|
|
149
|
+
Self {
|
|
150
|
+
count: 0,
|
|
151
|
+
username: String::from("Guest"),
|
|
152
|
+
is_logged_in: false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub type AppState = Mutex<AppStateInner>;
|
|
158
|
+
|
|
159
|
+
impl AppStateInner {
|
|
160
|
+
pub fn new() -> Self {
|
|
161
|
+
Self::default()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```rust
|
|
167
|
+
// ✅ src-tauri/src/main.rs
|
|
168
|
+
mod state;
|
|
169
|
+
|
|
170
|
+
use state::{AppState, AppStateInner};
|
|
171
|
+
use tauri::State;
|
|
172
|
+
|
|
173
|
+
#[tauri::command]
|
|
174
|
+
fn increment(state: State<'_, AppState>) -> i32 {
|
|
175
|
+
let mut state = state.lock().unwrap();
|
|
176
|
+
state.count += 1;
|
|
177
|
+
state.count
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[tauri::command]
|
|
181
|
+
fn login(state: State<'_, AppState>, username: String) {
|
|
182
|
+
let mut state = state.lock().unwrap();
|
|
183
|
+
state.username = username;
|
|
184
|
+
state.is_logged_in = true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn main() {
|
|
188
|
+
tauri::Builder::default()
|
|
189
|
+
.manage(AppState::new(AppStateInner::new()))
|
|
190
|
+
.invoke_handler(tauri::generate_handler![increment, login])
|
|
191
|
+
.run(tauri::generate_context!())
|
|
192
|
+
.expect("error while running tauri application");
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 추가 컨텍스트
|
|
197
|
+
|
|
198
|
+
**Mutex가 필요한 이유:**
|
|
199
|
+
|
|
200
|
+
| 상황 | Mutex 필요 여부 | 이유 |
|
|
201
|
+
|------|----------------|------|
|
|
202
|
+
| 읽기 전용 상태 | ❌ | `State<'_, T>` 자체가 읽기 전용 보장 |
|
|
203
|
+
| 가변 상태 | ✅ | 여러 스레드에서 동시 수정 가능 |
|
|
204
|
+
| 단일 스레드 | ⚠️ | Tauri는 멀티스레드 환경이므로 Mutex 필수 |
|
|
205
|
+
|
|
206
|
+
**타입 별칭의 장점:**
|
|
207
|
+
|
|
208
|
+
1. **타입 안전성**: 모든 커맨드에서 동일한 타입 사용
|
|
209
|
+
2. **가독성**: `State<'_, AppState>` vs `State<'_, Mutex<AppStateInner>>`
|
|
210
|
+
3. **유지보수**: 상태 구조 변경 시 타입 별칭만 수정
|
|
211
|
+
|
|
212
|
+
**Mutex::lock() 에러 처리:**
|
|
213
|
+
|
|
214
|
+
```rust
|
|
215
|
+
// ❌ unwrap() - 패닉 발생 가능
|
|
216
|
+
let mut state = state.lock().unwrap();
|
|
217
|
+
|
|
218
|
+
// ✅ Result 반환 - 에러 처리
|
|
219
|
+
#[tauri::command]
|
|
220
|
+
fn increment(state: State<'_, AppState>) -> Result<i32, String> {
|
|
221
|
+
let mut state = state.lock()
|
|
222
|
+
.map_err(|e| format!("Failed to lock state: {}", e))?;
|
|
223
|
+
state.count += 1;
|
|
224
|
+
Ok(state.count)
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**RwLock vs Mutex:**
|
|
229
|
+
|
|
230
|
+
```rust
|
|
231
|
+
// 읽기가 많고 쓰기가 적은 경우 RwLock 사용
|
|
232
|
+
use std::sync::RwLock;
|
|
233
|
+
|
|
234
|
+
type AppState = RwLock<AppStateInner>;
|
|
235
|
+
|
|
236
|
+
#[tauri::command]
|
|
237
|
+
fn get_count(state: State<'_, AppState>) -> i32 {
|
|
238
|
+
let state = state.read().unwrap(); // 여러 스레드 동시 읽기 가능
|
|
239
|
+
state.count
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[tauri::command]
|
|
243
|
+
fn increment(state: State<'_, AppState>) -> i32 {
|
|
244
|
+
let mut state = state.write().unwrap(); // 배타적 쓰기
|
|
245
|
+
state.count += 1;
|
|
246
|
+
state.count
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**패턴 비교:**
|
|
251
|
+
|
|
252
|
+
| 패턴 | 장점 | 단점 | 권장 |
|
|
253
|
+
|------|------|------|------|
|
|
254
|
+
| `Mutex<T>` | 단순, 안전 | 읽기도 락 필요 | 쓰기 많을 때 |
|
|
255
|
+
| `RwLock<T>` | 읽기 병렬화 | 복잡, 오버헤드 | 읽기 많을 때 |
|
|
256
|
+
| `Arc<Mutex<T>>` | 복제 가능 | 불필요 (Tauri가 관리) | ❌ 사용 안함 |
|
|
257
|
+
|
|
258
|
+
**참고:**
|
|
259
|
+
- Tauri State Management: [Managing State](https://tauri.app/v2/guides/features/state-management/)
|
|
260
|
+
- Rust Mutex: [std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
|
261
|
+
|
|
262
|
+
**영향도:**
|
|
263
|
+
- 안정성: HIGH (패닉 방지)
|
|
264
|
+
- 타입 안전성: HIGH (컴파일 타임 검증)
|
|
265
|
+
- 성능: NEUTRAL (Mutex 오버헤드 미미)
|