titanpl-sdk 2.0.3 → 2.0.4

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.
@@ -1,245 +1,284 @@
1
- use bytes::Bytes;
2
- use crossbeam::channel::{bounded, Sender};
3
- use std::thread;
4
- use std::sync::atomic::{AtomicUsize, Ordering};
5
- use tokio::sync::mpsc;
6
- use tokio::sync::oneshot;
7
- use smallvec::SmallVec;
8
-
9
- use crate::extensions::{self, TitanRuntime, AsyncOpRequest, WorkerAsyncResult};
10
-
11
- pub struct RuntimeManager {
12
- request_txs: Vec<Sender<WorkerCommand>>,
13
- round_robin_counter: AtomicUsize,
14
- _resume_txs: Vec<Sender<WorkerCommand>>, // Keep alive
15
- _workers: Vec<thread::JoinHandle<()>>,
16
- }
17
-
18
- pub enum WorkerCommand {
19
- Request(RequestTask),
20
- Resume {
21
- drift_id: u32,
22
- result: WorkerAsyncResult,
23
- },
24
- }
25
-
26
- #[allow(dead_code)]
27
- pub struct RequestTask {
28
- pub action_name: String,
29
- pub body: Option<Bytes>,
30
- pub method: String,
31
- pub path: String,
32
- pub headers: SmallVec<[(String, String); 8]>,
33
- pub params: SmallVec<[(String, String); 4]>,
34
- pub query: SmallVec<[(String, String); 4]>,
35
- pub response_tx: oneshot::Sender<WorkerResult>,
36
- }
37
-
38
- pub struct WorkerResult {
39
- pub json: serde_json::Value,
40
- pub timings: Vec<(String, f64)>,
41
- }
42
-
43
- impl RuntimeManager {
44
- pub fn new(project_root: std::path::PathBuf, num_threads: usize, stack_size: usize) -> Self {
45
- let (async_tx, mut async_rx) = mpsc::channel::<AsyncOpRequest>(1000);
46
-
47
- let tokio_handle = tokio::runtime::Handle::current();
48
-
49
- // Spawn Tokio Async Handler
50
- tokio_handle.spawn(async move {
51
- while let Some(req) = async_rx.recv().await {
52
- let drift_id = req.drift_id;
53
- let respond_tx = req.respond_tx;
54
- tokio::spawn(async move {
55
- let start = std::time::Instant::now();
56
- let result = extensions::builtin::run_async_operation(req.op).await;
57
- let duration_ms = start.elapsed().as_secs_f64() * 1000.0;
58
- let _ = respond_tx.send(WorkerAsyncResult {
59
- drift_id,
60
- result,
61
- duration_ms,
62
- });
63
- });
64
- }
65
- });
66
-
67
- let mut worker_txs = Vec::new();
68
- let mut workers = Vec::new();
69
-
70
- // Pass 1: Create channels
71
- for _ in 0..num_threads {
72
- let (tx, rx) = bounded(100);
73
- worker_txs.push((tx, rx));
74
- }
75
-
76
- let mut final_txs = Vec::new();
77
- for (tx, _) in &worker_txs {
78
- final_txs.push(tx.clone());
79
- }
80
-
81
- // Pass 2: Spawn Workers
82
- for (i, (tx, rx)) in worker_txs.into_iter().enumerate() {
83
- let my_tx = tx.clone(); // The worker needs a way to send commands to ITSELF (for resumes)
84
- let root = project_root.clone();
85
- let handle = tokio_handle.clone();
86
- let async_tx = async_tx.clone();
87
-
88
- let handle = thread::Builder::new()
89
- .name(format!("titan-worker-{}", i))
90
- .stack_size(stack_size)
91
- .spawn(move || {
92
- // Start a thread with a pinned V8 isolate.
93
- // This thread will handle requests for this isolate exclusively.
94
- let mut rt = extensions::init_runtime_worker(
95
- i,
96
- root,
97
- my_tx,
98
- handle,
99
- async_tx,
100
- stack_size
101
- );
102
-
103
- // Bind the runtime instance to the V8 isolate data slot
104
- // This is CRITICAL because native drift calls use this pointer.
105
- rt.bind_to_isolate();
106
-
107
- loop {
108
- match rx.recv() {
109
- Ok(cmd) => {
110
- match cmd {
111
- WorkerCommand::Request(task) => {
112
- handle_new_request(task, &mut rt);
113
- },
114
- WorkerCommand::Resume { drift_id, result } => {
115
- handle_resume(drift_id, result, &mut rt);
116
- }
117
- }
118
- }
119
- Err(_) => break, // Channel closed
120
- }
121
- }
122
- })
123
- .expect("Failed to spawn worker");
124
-
125
- workers.push(handle);
126
- }
127
-
128
- Self {
129
- request_txs: final_txs.clone(),
130
- round_robin_counter: AtomicUsize::new(0),
131
- _resume_txs: final_txs,
132
- _workers: workers,
133
- }
134
-
135
- }
136
-
137
- pub async fn execute(
138
- &self,
139
- action: String,
140
- method: String,
141
- path: String,
142
- body: Option<Bytes>,
143
- headers: SmallVec<[(String, String); 8]>,
144
- params: SmallVec<[(String, String); 4]>,
145
- query: SmallVec<[(String, String); 4]>,
146
- ) -> Result<(serde_json::Value, Vec<(String, f64)>), String> {
147
- let (tx, rx) = oneshot::channel();
148
- let task = RequestTask {
149
- action_name: action,
150
- body,
151
- method,
152
- path,
153
- headers,
154
- params,
155
- query,
156
- response_tx: tx,
157
- };
158
-
159
- // Round Robin Distribution
160
- let idx = self.round_robin_counter.fetch_add(1, Ordering::Relaxed) % self.request_txs.len();
161
- self.request_txs[idx].send(WorkerCommand::Request(task)).map_err(|e| e.to_string())?;
162
-
163
- match rx.await {
164
- Ok(res) => Ok((res.json, res.timings)),
165
- Err(_) => Err("Worker channel closed".to_string()),
166
- }
167
- }
168
- }
169
-
170
- // ----------------------------------------------------------------------------
171
- // HANDLERS (Simpler - No Mutex/Vec lookup)
172
- // ----------------------------------------------------------------------------
173
-
174
- fn handle_new_request(task: RequestTask, rt: &mut TitanRuntime) {
175
- rt.request_counter += 1;
176
- let request_id = rt.request_counter;
177
- rt.pending_requests.insert(request_id, task.response_tx);
178
-
179
- let req_data = extensions::RequestData {
180
- action_name: task.action_name.clone(),
181
- body: task.body.clone(),
182
- method: task.method.clone(),
183
- path: task.path.clone(),
184
- headers: task.headers.iter().map(|(k,v)| (k.clone(), v.clone())).collect(),
185
- params: task.params.iter().map(|(k,v)| (k.clone(), v.clone())).collect(),
186
- query: task.query.iter().map(|(k,v)| (k.clone(), v.clone())).collect(),
187
- };
188
- rt.active_requests.insert(request_id, req_data);
189
- let drift_count = rt.drift_counter;
190
- rt.request_start_counters.insert(request_id, drift_count);
191
-
192
- extensions::execute_action_optimized(
193
- rt,
194
- request_id,
195
- &task.action_name,
196
- task.body,
197
- &task.method,
198
- &task.path,
199
- &task.headers,
200
- &task.params,
201
- &task.query
202
- );
203
-
204
- // Cleanup if sync
205
- if !rt.pending_requests.contains_key(&request_id) {
206
- rt.active_requests.remove(&request_id);
207
- rt.request_start_counters.remove(&request_id);
208
- }
209
- }
210
-
211
- fn handle_resume(drift_id: u32, result: WorkerAsyncResult, rt: &mut TitanRuntime) {
212
- // 1. Identify which request this drift belongs to
213
- let req_id = rt.drift_to_request.get(&drift_id).copied().unwrap_or(0);
214
-
215
- // 2. Perform Timing
216
- let timing_type = if result.result.get("error").is_some() { "drift_error" } else { "drift" };
217
- rt.request_timings.entry(req_id).or_default().push((timing_type.to_string(), result.duration_ms));
218
-
219
- // 3. Store Result for Replay
220
- rt.completed_drifts.insert(drift_id, result.result);
221
-
222
- // 4. Trigger Replay
223
- if let Some(req_data) = rt.active_requests.get(&req_id).cloned() {
224
- let start_counter = rt.request_start_counters.get(&req_id).copied().unwrap_or(0);
225
- rt.drift_counter = start_counter;
226
-
227
- extensions::execute_action_optimized(
228
- rt,
229
- req_id,
230
- &req_data.action_name,
231
- req_data.body,
232
- &req_data.method,
233
- &req_data.path,
234
- &req_data.headers,
235
- &req_data.params,
236
- &req_data.query
237
- );
238
- }
239
-
240
- // 5. Cleanup
241
- if req_id != 0 && !rt.pending_requests.contains_key(&req_id) {
242
- rt.active_requests.remove(&req_id);
243
- rt.request_start_counters.remove(&req_id);
244
- }
245
- }
1
+ //! Worker Pool Management (Performance Optimized)
2
+ //!
3
+ //! Features:
4
+ //! 1. Work-stealing fallback strategy.
5
+ //! 2. Bounded channel capacity for pipeline handling.
6
+ //! 3. Batch-ready architecture for HTTP pipelining.
7
+ //! 4. Zero-copy / deferred cloning where possible.
8
+
9
+ use bytes::Bytes;
10
+ use crossbeam::channel::{bounded, Sender, TrySendError};
11
+ use std::sync::atomic::{AtomicUsize, Ordering};
12
+ use std::thread;
13
+ use tokio::sync::mpsc;
14
+ use tokio::sync::oneshot;
15
+ use smallvec::SmallVec;
16
+
17
+ use crate::extensions::{self, AsyncOpRequest, TitanRuntime, WorkerAsyncResult};
18
+
19
+ pub struct RuntimeManager {
20
+ request_txs: Vec<Sender<WorkerCommand>>,
21
+ round_robin_counter: AtomicUsize,
22
+ num_workers: usize,
23
+ _workers: Vec<thread::JoinHandle<()>>,
24
+ }
25
+
26
+ pub enum WorkerCommand {
27
+ Request(RequestTask),
28
+ Resume {
29
+ drift_id: u32,
30
+ result: WorkerAsyncResult,
31
+ },
32
+ }
33
+
34
+ #[allow(dead_code)]
35
+ pub struct RequestTask {
36
+ pub action_name: String,
37
+ pub body: Option<Bytes>,
38
+ pub method: String,
39
+ pub path: String,
40
+ pub headers: SmallVec<[(String, String); 8]>,
41
+ pub params: SmallVec<[(String, String); 4]>,
42
+ pub query: SmallVec<[(String, String); 4]>,
43
+ pub response_tx: oneshot::Sender<WorkerResult>,
44
+ }
45
+
46
+ pub struct WorkerResult {
47
+ pub json: serde_json::Value,
48
+ pub timings: Vec<(String, f64)>,
49
+ }
50
+
51
+ impl RuntimeManager {
52
+ pub fn new(
53
+ project_root: std::path::PathBuf,
54
+ num_threads: usize,
55
+ stack_size: usize,
56
+ ) -> Self {
57
+ let (async_tx, mut async_rx) = mpsc::channel::<AsyncOpRequest>(2048);
58
+ let tokio_handle = tokio::runtime::Handle::current();
59
+
60
+ // Spawn Tokio Async Handler (for drift operations)
61
+ tokio_handle.spawn(async move {
62
+ while let Some(req) = async_rx.recv().await {
63
+ let drift_id = req.drift_id;
64
+ let respond_tx = req.respond_tx;
65
+ tokio::spawn(async move {
66
+ let start = std::time::Instant::now();
67
+ let result = extensions::builtin::run_async_operation(req.op).await;
68
+ let duration_ms = start.elapsed().as_secs_f64() * 1000.0;
69
+ let _ = respond_tx.send(WorkerAsyncResult {
70
+ drift_id,
71
+ result,
72
+ duration_ms,
73
+ });
74
+ });
75
+ }
76
+ });
77
+
78
+ // Create worker channels
79
+ let channel_capacity = 256;
80
+ let mut workers = Vec::with_capacity(num_threads);
81
+
82
+ let mut channels: Vec<(Sender<WorkerCommand>, crossbeam::channel::Receiver<WorkerCommand>)> =
83
+ Vec::with_capacity(num_threads);
84
+
85
+ for _ in 0..num_threads {
86
+ let (tx, rx) = bounded(channel_capacity);
87
+ channels.push((tx, rx));
88
+ }
89
+
90
+ let mut final_txs: Vec<Sender<WorkerCommand>> = Vec::with_capacity(num_threads);
91
+ for (tx, _) in &channels {
92
+ final_txs.push(tx.clone());
93
+ }
94
+
95
+ // Spawn Worker Threads
96
+ for (i, (tx, rx)) in channels.into_iter().enumerate() {
97
+ let my_tx = tx.clone();
98
+ let root = project_root.clone();
99
+ let handle = tokio_handle.clone();
100
+ let async_tx = async_tx.clone();
101
+
102
+ let handle = thread::Builder::new()
103
+ .name(format!("titan-worker-{}", i))
104
+ .stack_size(stack_size)
105
+ .spawn(move || {
106
+ let mut rt = extensions::init_runtime_worker(
107
+ i,
108
+ root,
109
+ my_tx,
110
+ handle,
111
+ async_tx,
112
+ stack_size,
113
+ );
114
+ rt.bind_to_isolate();
115
+
116
+ loop {
117
+ match rx.recv() {
118
+ Ok(cmd) => match cmd {
119
+ WorkerCommand::Request(task) => {
120
+ handle_new_request(task, &mut rt);
121
+ }
122
+ WorkerCommand::Resume { drift_id, result } => {
123
+ handle_resume(drift_id, result, &mut rt);
124
+ }
125
+ },
126
+ Err(_) => break,
127
+ }
128
+ }
129
+ })
130
+ .expect("Failed to spawn worker");
131
+
132
+ workers.push(handle);
133
+ }
134
+
135
+ Self {
136
+ request_txs: final_txs,
137
+ round_robin_counter: AtomicUsize::new(0),
138
+ num_workers: num_threads,
139
+ _workers: workers,
140
+ }
141
+ }
142
+
143
+ /// Execute an action on a worker. Uses round-robin with work-stealing fallback.
144
+ pub async fn execute(
145
+ &self,
146
+ action: String,
147
+ method: String,
148
+ path: String,
149
+ body: Option<Bytes>,
150
+ headers: SmallVec<[(String, String); 8]>,
151
+ params: SmallVec<[(String, String); 4]>,
152
+ query: SmallVec<[(String, String); 4]>,
153
+ ) -> Result<(serde_json::Value, Vec<(String, f64)>), String> {
154
+ let (tx, rx) = oneshot::channel();
155
+ let task = RequestTask {
156
+ action_name: action,
157
+ body,
158
+ method,
159
+ path,
160
+ headers,
161
+ params,
162
+ query,
163
+ response_tx: tx,
164
+ };
165
+
166
+ // Work-Stealing Distribution
167
+ let start_idx = self.round_robin_counter.fetch_add(1, Ordering::Relaxed) % self.num_workers;
168
+ let mut cmd = WorkerCommand::Request(task);
169
+
170
+ for attempt in 0..self.num_workers {
171
+ let idx = (start_idx + attempt) % self.num_workers;
172
+ match self.request_txs[idx].try_send(cmd) {
173
+ Ok(()) => {
174
+ return match rx.await {
175
+ Ok(res) => Ok((res.json, res.timings)),
176
+ Err(_) => Err("Worker channel closed".to_string()),
177
+ };
178
+ }
179
+ Err(TrySendError::Full(returned)) => {
180
+ cmd = returned;
181
+ }
182
+ Err(TrySendError::Disconnected(_)) => {
183
+ return Err("Worker disconnected".to_string());
184
+ }
185
+ }
186
+ }
187
+
188
+ // All workers full — blocking send to the original target as last resort
189
+ self.request_txs[start_idx]
190
+ .send(cmd)
191
+ .map_err(|e| e.to_string())?;
192
+
193
+ match rx.await {
194
+ Ok(res) => Ok((res.json, res.timings)),
195
+ Err(_) => Err("Worker channel closed".to_string()),
196
+ }
197
+ }
198
+ }
199
+
200
+ /// Handle a new incoming request.
201
+ ///
202
+ /// OPTIMIZATION: Deferred cloning.
203
+ /// Only stores data if drift (async suspend) happens.
204
+ fn handle_new_request(task: RequestTask, rt: &mut TitanRuntime) {
205
+ rt.request_counter += 1;
206
+ let request_id = rt.request_counter;
207
+
208
+ // Move response_tx into pending (partial move of task — other fields remain accessible)
209
+ rt.pending_requests.insert(request_id, task.response_tx);
210
+
211
+ let drift_count = rt.drift_counter;
212
+ rt.request_start_counters.insert(request_id, drift_count);
213
+
214
+ // Execute action — pass references, body is O(1) Bytes clone
215
+ extensions::execute_action_optimized(
216
+ rt,
217
+ request_id,
218
+ &task.action_name,
219
+ task.body.clone(), // Bytes::clone() is O(1) refcount bump
220
+ &task.method,
221
+ &task.path,
222
+ &task.headers,
223
+ &task.params,
224
+ &task.query,
225
+ );
226
+
227
+ // Deferred cloning decision
228
+ if !rt.pending_requests.contains_key(&request_id) {
229
+ // Completed synchronously — no data needed, minimal cleanup
230
+ rt.request_start_counters.remove(&request_id);
231
+ } else {
232
+ // Suspended via drift — MOVE (not clone) data for resume replay.
233
+ rt.active_requests.insert(
234
+ request_id,
235
+ extensions::RequestData {
236
+ action_name: task.action_name,
237
+ body: task.body,
238
+ method: task.method,
239
+ path: task.path,
240
+ headers: task.headers.into_vec(),
241
+ params: task.params.into_vec(),
242
+ query: task.query.into_vec(),
243
+ },
244
+ );
245
+ }
246
+ }
247
+
248
+ fn handle_resume(drift_id: u32, result: WorkerAsyncResult, rt: &mut TitanRuntime) {
249
+ let req_id = rt.drift_to_request.get(&drift_id).copied().unwrap_or(0);
250
+
251
+ let timing_type = if result.result.get("error").is_some() {
252
+ "drift_error"
253
+ } else {
254
+ "drift"
255
+ };
256
+ rt.request_timings
257
+ .entry(req_id)
258
+ .or_default()
259
+ .push((timing_type.to_string(), result.duration_ms));
260
+
261
+ rt.completed_drifts.insert(drift_id, result.result);
262
+
263
+ if let Some(req_data) = rt.active_requests.get(&req_id).cloned() {
264
+ let start_counter = rt.request_start_counters.get(&req_id).copied().unwrap_or(0);
265
+ rt.drift_counter = start_counter;
266
+
267
+ extensions::execute_action_optimized(
268
+ rt,
269
+ req_id,
270
+ &req_data.action_name,
271
+ req_data.body,
272
+ &req_data.method,
273
+ &req_data.path,
274
+ &req_data.headers,
275
+ &req_data.params,
276
+ &req_data.query,
277
+ );
278
+ }
279
+
280
+ if req_id != 0 && !rt.pending_requests.contains_key(&req_id) {
281
+ rt.active_requests.remove(&req_id);
282
+ rt.request_start_counters.remove(&req_id);
283
+ }
284
+ }
@@ -1,3 +1,5 @@
1
+ //! Terminal styling and utility functions.
2
+
1
3
  pub fn blue(s: &str) -> String {
2
4
  format!("\x1b[38;5;39m{}\x1b[0m", s)
3
5
  }
@@ -29,5 +31,3 @@ pub fn parse_expires_in(value: &str) -> Option<u64> {
29
31
  _ => None,
30
32
  }
31
33
  }
32
-
33
-