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.
- package/README.md +2 -0
- package/package.json +38 -34
- package/templates/app/app.js +1 -4
- package/templates/server/Cargo.toml +22 -2
- package/templates/server/src/action_management.rs +8 -10
- package/templates/server/src/errors.rs +3 -1
- package/templates/server/src/extensions/builtin.rs +1038 -877
- package/templates/server/src/extensions/external.rs +338 -404
- package/templates/server/src/extensions/mod.rs +580 -448
- package/templates/server/src/extensions/titan_core.js +249 -186
- package/templates/server/src/fast_path.rs +719 -0
- package/templates/server/src/main.rs +370 -169
- package/templates/server/src/runtime.rs +284 -245
- package/templates/server/src/utils.rs +2 -2
- package/templates/titan/bundle.js +259 -264
- package/templates/titan/dev.js +46 -6
- package/templates/titan/error-box.js +277 -268
- package/templates/app/titan.d.ts +0 -87
- package/templates/index.d.ts +0 -249
|
@@ -1,245 +1,284 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
use
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
pub
|
|
39
|
-
pub
|
|
40
|
-
pub
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
}
|