@zap-js/server 0.0.2 → 0.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/index.js +18 -14
- package/package.json +4 -9
- package/src/bin/zap.rs +0 -154
- package/src/config.rs +0 -253
- package/src/connection_pool.rs +0 -404
- package/src/error.rs +0 -380
- package/src/handler.rs +0 -89
- package/src/ipc.js +0 -10
- package/src/ipc.rs +0 -499
- package/src/lib.rs +0 -433
- package/src/metrics.rs +0 -264
- package/src/proxy.rs +0 -436
- package/src/reliability.rs +0 -917
- package/src/request.rs +0 -60
- package/src/request_id.rs +0 -97
- package/src/response.rs +0 -182
- package/src/rpc.js +0 -14
- package/src/server.rs +0 -597
- package/src/static.rs +0 -572
- package/src/types.js +0 -21
- package/src/utils.rs +0 -18
- package/src/websocket.rs +0 -429
package/src/connection_pool.rs
DELETED
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
//! IPC Connection Pool
|
|
2
|
-
//!
|
|
3
|
-
//! Provides a pool of persistent IPC connections to the TypeScript runtime.
|
|
4
|
-
//! This eliminates per-request connection overhead, significantly improving
|
|
5
|
-
//! throughput for handler invocations.
|
|
6
|
-
//!
|
|
7
|
-
//! Features:
|
|
8
|
-
//! - Pool of N persistent connections (default: 4)
|
|
9
|
-
//! - Health checks before use
|
|
10
|
-
//! - Automatic reconnection on failure
|
|
11
|
-
//! - Connection timeout handling
|
|
12
|
-
//! - Fair connection distribution
|
|
13
|
-
|
|
14
|
-
use crate::error::{ZapError, ZapResult};
|
|
15
|
-
use crate::ipc::{IpcClient, IpcEncoding, IpcMessage};
|
|
16
|
-
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
17
|
-
use std::sync::Arc;
|
|
18
|
-
use std::time::Duration;
|
|
19
|
-
use tokio::sync::{Mutex, Semaphore};
|
|
20
|
-
use tracing::{debug, error, warn};
|
|
21
|
-
|
|
22
|
-
/// Default number of connections in the pool
|
|
23
|
-
const DEFAULT_POOL_SIZE: usize = 4;
|
|
24
|
-
|
|
25
|
-
/// Default connection timeout in seconds
|
|
26
|
-
const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 5;
|
|
27
|
-
|
|
28
|
-
/// Default health check interval in seconds
|
|
29
|
-
const HEALTH_CHECK_INTERVAL_SECS: u64 = 30;
|
|
30
|
-
|
|
31
|
-
/// A pooled connection wrapper
|
|
32
|
-
struct PooledConnection {
|
|
33
|
-
client: Option<IpcClient>,
|
|
34
|
-
last_used: std::time::Instant,
|
|
35
|
-
healthy: bool,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
impl PooledConnection {
|
|
39
|
-
fn new() -> Self {
|
|
40
|
-
Self {
|
|
41
|
-
client: None,
|
|
42
|
-
last_used: std::time::Instant::now(),
|
|
43
|
-
healthy: false,
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
fn is_valid(&self) -> bool {
|
|
48
|
-
self.client.is_some() && self.healthy
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/// Configuration for the connection pool
|
|
53
|
-
#[derive(Clone)]
|
|
54
|
-
pub struct PoolConfig {
|
|
55
|
-
/// Number of connections in the pool
|
|
56
|
-
pub size: usize,
|
|
57
|
-
/// Connection timeout
|
|
58
|
-
pub connect_timeout: Duration,
|
|
59
|
-
/// Socket path for IPC
|
|
60
|
-
pub socket_path: String,
|
|
61
|
-
/// IPC encoding format
|
|
62
|
-
pub encoding: IpcEncoding,
|
|
63
|
-
/// Health check interval
|
|
64
|
-
pub health_check_interval: Duration,
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
impl Default for PoolConfig {
|
|
68
|
-
fn default() -> Self {
|
|
69
|
-
Self {
|
|
70
|
-
size: DEFAULT_POOL_SIZE,
|
|
71
|
-
connect_timeout: Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS),
|
|
72
|
-
socket_path: String::new(),
|
|
73
|
-
encoding: IpcEncoding::default(),
|
|
74
|
-
health_check_interval: Duration::from_secs(HEALTH_CHECK_INTERVAL_SECS),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
impl PoolConfig {
|
|
80
|
-
/// Create a new pool configuration with the given socket path
|
|
81
|
-
pub fn new(socket_path: String) -> Self {
|
|
82
|
-
Self {
|
|
83
|
-
socket_path,
|
|
84
|
-
..Default::default()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/// Set the pool size
|
|
89
|
-
pub fn size(mut self, size: usize) -> Self {
|
|
90
|
-
self.size = size;
|
|
91
|
-
self
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// Set the connect timeout
|
|
95
|
-
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
|
|
96
|
-
self.connect_timeout = timeout;
|
|
97
|
-
self
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/// Set the encoding format
|
|
101
|
-
pub fn encoding(mut self, encoding: IpcEncoding) -> Self {
|
|
102
|
-
self.encoding = encoding;
|
|
103
|
-
self
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/// IPC Connection Pool
|
|
108
|
-
///
|
|
109
|
-
/// Manages a pool of persistent connections to the TypeScript IPC server.
|
|
110
|
-
/// Connections are reused across requests to eliminate connection overhead.
|
|
111
|
-
pub struct ConnectionPool {
|
|
112
|
-
/// Pool configuration
|
|
113
|
-
config: PoolConfig,
|
|
114
|
-
/// Pooled connections (each wrapped in Mutex for exclusive access)
|
|
115
|
-
connections: Vec<Arc<Mutex<PooledConnection>>>,
|
|
116
|
-
/// Semaphore to limit concurrent connection acquisition
|
|
117
|
-
semaphore: Arc<Semaphore>,
|
|
118
|
-
/// Round-robin index for fair distribution
|
|
119
|
-
next_index: AtomicUsize,
|
|
120
|
-
/// Whether the pool is initialized
|
|
121
|
-
initialized: std::sync::atomic::AtomicBool,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
impl ConnectionPool {
|
|
125
|
-
/// Create a new connection pool with the given configuration
|
|
126
|
-
pub fn new(config: PoolConfig) -> Self {
|
|
127
|
-
let connections = (0..config.size)
|
|
128
|
-
.map(|_| Arc::new(Mutex::new(PooledConnection::new())))
|
|
129
|
-
.collect();
|
|
130
|
-
|
|
131
|
-
Self {
|
|
132
|
-
semaphore: Arc::new(Semaphore::new(config.size)),
|
|
133
|
-
connections,
|
|
134
|
-
config,
|
|
135
|
-
next_index: AtomicUsize::new(0),
|
|
136
|
-
initialized: std::sync::atomic::AtomicBool::new(false),
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/// Create a pool with the given socket path and default settings
|
|
141
|
-
pub fn with_socket(socket_path: String) -> Self {
|
|
142
|
-
Self::new(PoolConfig::new(socket_path))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/// Initialize the connection pool by establishing all connections
|
|
146
|
-
pub async fn initialize(&self) -> ZapResult<()> {
|
|
147
|
-
if self.initialized.load(Ordering::Acquire) {
|
|
148
|
-
return Ok(());
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
debug!("Initializing connection pool with {} connections", self.config.size);
|
|
152
|
-
|
|
153
|
-
let mut init_count = 0;
|
|
154
|
-
for (i, conn_mutex) in self.connections.iter().enumerate() {
|
|
155
|
-
let mut conn = conn_mutex.lock().await;
|
|
156
|
-
match self.create_connection().await {
|
|
157
|
-
Ok(client) => {
|
|
158
|
-
conn.client = Some(client);
|
|
159
|
-
conn.healthy = true;
|
|
160
|
-
conn.last_used = std::time::Instant::now();
|
|
161
|
-
init_count += 1;
|
|
162
|
-
debug!("Connection {} initialized", i);
|
|
163
|
-
}
|
|
164
|
-
Err(e) => {
|
|
165
|
-
warn!("Failed to initialize connection {}: {}", i, e);
|
|
166
|
-
// Continue - we'll try to reconnect later
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if init_count == 0 {
|
|
172
|
-
return Err(ZapError::ipc("Failed to initialize any pool connections"));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
self.initialized.store(true, Ordering::Release);
|
|
176
|
-
debug!("Connection pool initialized with {}/{} connections", init_count, self.config.size);
|
|
177
|
-
|
|
178
|
-
Ok(())
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/// Create a new IPC connection
|
|
182
|
-
async fn create_connection(&self) -> ZapResult<IpcClient> {
|
|
183
|
-
let timeout = self.config.connect_timeout;
|
|
184
|
-
|
|
185
|
-
tokio::time::timeout(
|
|
186
|
-
timeout,
|
|
187
|
-
IpcClient::connect_with_encoding(&self.config.socket_path, self.config.encoding),
|
|
188
|
-
)
|
|
189
|
-
.await
|
|
190
|
-
.map_err(|_| ZapError::timeout("Connection pool connect timeout", timeout.as_millis() as u64))?
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/// Get a connection from the pool, reconnecting if necessary
|
|
194
|
-
async fn get_connection_index(&self) -> ZapResult<usize> {
|
|
195
|
-
// Round-robin selection with wrap-around
|
|
196
|
-
let index = self.next_index.fetch_add(1, Ordering::Relaxed) % self.config.size;
|
|
197
|
-
Ok(index)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/// Execute a request-response operation using a pooled connection
|
|
201
|
-
///
|
|
202
|
-
/// This method handles:
|
|
203
|
-
/// - Connection acquisition from pool
|
|
204
|
-
/// - Automatic reconnection on failure
|
|
205
|
-
/// - Connection release back to pool
|
|
206
|
-
pub async fn send_recv(&self, message: IpcMessage) -> ZapResult<IpcMessage> {
|
|
207
|
-
// Acquire semaphore permit (limits concurrent usage)
|
|
208
|
-
let _permit = self.semaphore.acquire().await.map_err(|_| {
|
|
209
|
-
ZapError::ipc("Connection pool semaphore closed")
|
|
210
|
-
})?;
|
|
211
|
-
|
|
212
|
-
// Get a connection index
|
|
213
|
-
let index = self.get_connection_index().await?;
|
|
214
|
-
let conn_mutex = &self.connections[index];
|
|
215
|
-
|
|
216
|
-
// Try with the existing connection first
|
|
217
|
-
let mut conn = conn_mutex.lock().await;
|
|
218
|
-
|
|
219
|
-
// Check if connection is valid
|
|
220
|
-
if !conn.is_valid() {
|
|
221
|
-
debug!("Connection {} invalid, reconnecting", index);
|
|
222
|
-
match self.create_connection().await {
|
|
223
|
-
Ok(client) => {
|
|
224
|
-
conn.client = Some(client);
|
|
225
|
-
conn.healthy = true;
|
|
226
|
-
}
|
|
227
|
-
Err(e) => {
|
|
228
|
-
conn.healthy = false;
|
|
229
|
-
return Err(e);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Send and receive
|
|
235
|
-
if let Some(client) = &mut conn.client {
|
|
236
|
-
match client.send_recv(message.clone()).await {
|
|
237
|
-
Ok(response) => {
|
|
238
|
-
conn.last_used = std::time::Instant::now();
|
|
239
|
-
Ok(response)
|
|
240
|
-
}
|
|
241
|
-
Err(e) => {
|
|
242
|
-
// Connection failed, mark as unhealthy
|
|
243
|
-
warn!("Connection {} failed: {}, marking unhealthy", index, e);
|
|
244
|
-
conn.healthy = false;
|
|
245
|
-
conn.client = None;
|
|
246
|
-
|
|
247
|
-
// Try to reconnect and retry once
|
|
248
|
-
match self.create_connection().await {
|
|
249
|
-
Ok(mut new_client) => {
|
|
250
|
-
match new_client.send_recv(message).await {
|
|
251
|
-
Ok(response) => {
|
|
252
|
-
conn.client = Some(new_client);
|
|
253
|
-
conn.healthy = true;
|
|
254
|
-
conn.last_used = std::time::Instant::now();
|
|
255
|
-
Ok(response)
|
|
256
|
-
}
|
|
257
|
-
Err(retry_err) => {
|
|
258
|
-
error!("Retry also failed: {}", retry_err);
|
|
259
|
-
Err(retry_err)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
Err(reconnect_err) => {
|
|
264
|
-
error!("Reconnect failed: {}", reconnect_err);
|
|
265
|
-
Err(reconnect_err)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
Err(ZapError::ipc("No connection available"))
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/// Perform health check on all connections
|
|
276
|
-
pub async fn health_check(&self) -> (usize, usize) {
|
|
277
|
-
let mut healthy = 0;
|
|
278
|
-
let mut total = 0;
|
|
279
|
-
|
|
280
|
-
for conn_mutex in &self.connections {
|
|
281
|
-
total += 1;
|
|
282
|
-
let conn = conn_mutex.lock().await;
|
|
283
|
-
if conn.is_valid() {
|
|
284
|
-
healthy += 1;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
(healthy, total)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/// Close all connections in the pool
|
|
292
|
-
pub async fn close(&self) {
|
|
293
|
-
debug!("Closing connection pool");
|
|
294
|
-
|
|
295
|
-
for conn_mutex in &self.connections {
|
|
296
|
-
let mut conn = conn_mutex.lock().await;
|
|
297
|
-
conn.client = None;
|
|
298
|
-
conn.healthy = false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
self.initialized.store(false, Ordering::Release);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/// Get pool configuration
|
|
305
|
-
pub fn config(&self) -> &PoolConfig {
|
|
306
|
-
&self.config
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/// Get pool statistics
|
|
310
|
-
pub fn stats(&self) -> PoolStats {
|
|
311
|
-
PoolStats {
|
|
312
|
-
size: self.config.size,
|
|
313
|
-
initialized: self.initialized.load(Ordering::Acquire),
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/// Pool statistics
|
|
319
|
-
#[derive(Debug, Clone)]
|
|
320
|
-
pub struct PoolStats {
|
|
321
|
-
pub size: usize,
|
|
322
|
-
pub initialized: bool,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/// Global connection pool singleton
|
|
326
|
-
static GLOBAL_POOL: std::sync::OnceLock<Arc<ConnectionPool>> = std::sync::OnceLock::new();
|
|
327
|
-
|
|
328
|
-
/// Initialize the global connection pool
|
|
329
|
-
pub fn init_global_pool(socket_path: String) -> ZapResult<Arc<ConnectionPool>> {
|
|
330
|
-
let pool = Arc::new(ConnectionPool::with_socket(socket_path));
|
|
331
|
-
|
|
332
|
-
match GLOBAL_POOL.set(pool.clone()) {
|
|
333
|
-
Ok(()) => Ok(pool),
|
|
334
|
-
Err(_) => {
|
|
335
|
-
// Pool already initialized, return existing
|
|
336
|
-
Ok(GLOBAL_POOL.get().unwrap().clone())
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/// Initialize the global connection pool with custom config
|
|
342
|
-
pub fn init_global_pool_with_config(config: PoolConfig) -> ZapResult<Arc<ConnectionPool>> {
|
|
343
|
-
let pool = Arc::new(ConnectionPool::new(config));
|
|
344
|
-
|
|
345
|
-
match GLOBAL_POOL.set(pool.clone()) {
|
|
346
|
-
Ok(()) => Ok(pool),
|
|
347
|
-
Err(_) => {
|
|
348
|
-
// Pool already initialized, return existing
|
|
349
|
-
Ok(GLOBAL_POOL.get().unwrap().clone())
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/// Get the global connection pool (must be initialized first)
|
|
355
|
-
pub fn get_global_pool() -> Option<Arc<ConnectionPool>> {
|
|
356
|
-
GLOBAL_POOL.get().cloned()
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
#[cfg(test)]
|
|
360
|
-
mod tests {
|
|
361
|
-
use super::*;
|
|
362
|
-
|
|
363
|
-
#[test]
|
|
364
|
-
fn test_pool_config_builder() {
|
|
365
|
-
let config = PoolConfig::new("/tmp/test.sock".to_string())
|
|
366
|
-
.size(8)
|
|
367
|
-
.connect_timeout(Duration::from_secs(10))
|
|
368
|
-
.encoding(IpcEncoding::Json);
|
|
369
|
-
|
|
370
|
-
assert_eq!(config.size, 8);
|
|
371
|
-
assert_eq!(config.connect_timeout, Duration::from_secs(10));
|
|
372
|
-
assert_eq!(config.encoding, IpcEncoding::Json);
|
|
373
|
-
assert_eq!(config.socket_path, "/tmp/test.sock");
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
#[test]
|
|
377
|
-
fn test_pool_creation() {
|
|
378
|
-
let pool = ConnectionPool::with_socket("/tmp/test.sock".to_string());
|
|
379
|
-
|
|
380
|
-
assert_eq!(pool.config().size, DEFAULT_POOL_SIZE);
|
|
381
|
-
assert_eq!(pool.connections.len(), DEFAULT_POOL_SIZE);
|
|
382
|
-
assert!(!pool.initialized.load(Ordering::Acquire));
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
#[test]
|
|
386
|
-
fn test_pool_stats() {
|
|
387
|
-
let pool = ConnectionPool::with_socket("/tmp/test.sock".to_string());
|
|
388
|
-
let stats = pool.stats();
|
|
389
|
-
|
|
390
|
-
assert_eq!(stats.size, DEFAULT_POOL_SIZE);
|
|
391
|
-
assert!(!stats.initialized);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
#[tokio::test]
|
|
395
|
-
async fn test_round_robin_index() {
|
|
396
|
-
let pool = ConnectionPool::new(PoolConfig::new("/tmp/test.sock".to_string()).size(4));
|
|
397
|
-
|
|
398
|
-
// Test round-robin distribution
|
|
399
|
-
for expected in 0..12 {
|
|
400
|
-
let index = pool.get_connection_index().await.unwrap();
|
|
401
|
-
assert_eq!(index, expected % 4);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|