swadhinbiswas

[email protected]
2026-08-01 08:03:38 +0600 committed NaN years ago
952cccf
commit 952cccf
7 files changed
+94 -83
▼crates/justapi-core/src/compress.rs
+3-0
317
317
318
318
#[test]
319
319
fn test_encoding_from_wildcard() {
320
+ #[cfg(feature = "brotli-compression")]
321
+ assert_eq!(Encoding::from_accept_encoding("*"), Encoding::Brotli);
322
+ #[cfg(not(feature = "brotli-compression"))]
320
323
assert_eq!(Encoding::from_accept_encoding("*"), Encoding::Gzip);
321
324
}
322
325
323
326
▼crates/justapi-core/src/health.rs
+5-1
79
79
/// Run all registered checks and return a health report.
80
80
/// Checks are run in parallel with a per-check timeout of 5 seconds.
81
81
pub async fn check_all(&self) -> HealthReport {
82
- let check_timeout = std::time::Duration::from_secs(5);
82
+ self.check_all_with_timeout(std::time::Duration::from_secs(5)).await
83
+ }
84
+
85
+ /// Run all registered checks with a custom per-check timeout.
86
+ pub async fn check_all_with_timeout(&self, check_timeout: std::time::Duration) -> HealthReport {
83
87
let futs: Vec<_> = self
84
88
.checks
85
89
.iter()
86
90
▼crates/justapi-core/src/openai.rs
+2-2
815
815
#[test]
816
816
fn test_safe_status_invalid_code_falls_back_to_500() {
817
817
assert_eq!(safe_status(0), StatusCode::INTERNAL_SERVER_ERROR);
818
- assert_eq!(safe_status(999), StatusCode::INTERNAL_SERVER_ERROR);
819
- assert_eq!(safe_status(70000), StatusCode::INTERNAL_SERVER_ERROR);
818
+ assert_eq!(safe_status(99), StatusCode::INTERNAL_SERVER_ERROR);
819
+ assert_eq!(safe_status(6000), StatusCode::INTERNAL_SERVER_ERROR);
820
820
assert_eq!(safe_status(u16::MAX), StatusCode::INTERNAL_SERVER_ERROR);
821
821
}
822
822
}
823
823
▼crates/justapi-core/src/resilience.rs
+2-2
752
752
async fn test_timeout_returns_504() {
753
753
let slow_handler: HandlerFn<TestBody> = Arc::new(|_req: Request<TestBody>| {
754
754
Box::pin(async {
755
- tokio::time::sleep(Duration::from_secs(10)).await;
755
+ tokio::time::sleep(Duration::from_millis(200)).await;
756
756
Ok(json_response(StatusCode::OK, r#"{"ok":true}"#))
757
757
})
758
758
});
1451
1451
1452
1452
let slow_handler: HandlerFn<TestBody> = Arc::new(|_req: Request<TestBody>| {
1453
1453
Box::pin(async {
1454
- tokio::time::sleep(Duration::from_secs(10)).await;
1454
+ tokio::time::sleep(Duration::from_millis(500)).await;
1455
1455
Ok(json_response(StatusCode::OK, "ok"))
1456
1456
})
1457
1457
});
1458
1458
▼crates/justapi-core/src/router.rs
+6-4
381
381
let _ = router.at(&Method::GET, "/api/v3/users/42/posts/84");
382
382
}
383
383
384
- let iterations = 100_000u64;
384
+ let iterations = if cfg!(miri) { 50u64 } else { 100_000u64 };
385
385
let mut total_ns = 0u64;
386
386
387
387
let test_paths = [
405
405
println!("500-route table: {} lookups, avg {}ns", total_lookups, avg_ns,);
406
406
407
407
// Benchmark gate: average lookup should be under 2us (release) / 5us (debug)
408
- // CI runners are slower than local machines, so we use a generous tolerance
409
- let target = if cfg!(debug_assertions) { 5000 } else { 2000 };
410
- assert!(avg_ns < target, "Route lookup too slow: {}ns (target < {}ns)", avg_ns, target);
408
+ // Skip threshold check in Miri interpreter mode
409
+ if !cfg!(miri) {
410
+ let target = if cfg!(debug_assertions) { 5000 } else { 2000 };
411
+ assert!(avg_ns < target, "Route lookup too slow: {}ns (target < {}ns)", avg_ns, target);
412
+ }
411
413
}
412
414
}
413
415
▼crates/justapi-core/src/server/mod.rs
+74-72
1610
1610
conn_metrics.connection_closed();
1611
1611
}
1612
1612
1613
-async fn serve_http(
1614
- listener: TcpListener,
1613
+/// Shared accept loop logic for TCP, UDS, and TLS servers.
1614
+/// Accepts connections from the listener, spawns `serve_connection` for each,
1615
+/// and handles graceful shutdown.
1616
+async fn serve_generic<S>(
1617
+ mut accept_fn: impl FnMut() -> std::pin::Pin<
1618
+ Box<dyn std::future::Future<Output = Result<(S, Option<SocketAddr>)>> + Send>,
1619
+ >,
1615
1620
chain: Arc<MiddlewareChain>,
1616
1621
static_dir: Option<StaticDir>,
1617
1622
static_mounts: Vec<StaticMount>,
1620
1625
shutdown_timeout: std::time::Duration,
1621
1626
#[cfg(feature = "wasm")] wasm_middleware: Option<Arc<crate::wasm::WasmEngine>>,
1622
1627
#[cfg(feature = "ws")] ws_handler: Option<WsHandler>,
1623
-) -> Result<()> {
1624
- // **IMPORTANT:** This duplicates `serve_unix` below. A future refactoring
1625
- // should extract the common accept loop into a generic function parameterized
1626
- // over the listener type. See P2-3 in deepanalysis.md.
1628
+) -> Result<()>
1629
+where
1630
+ S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
1631
+{
1627
1632
let mut connections = tokio::task::JoinSet::new();
1628
1633
let conn_semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(max_connections()));
1629
1634
1632
1637
1633
1638
let (stream, peer) = if let Some(token) = &token {
1634
1639
tokio::select! {
1635
- result = listener.accept() => result?,
1640
+ result = accept_fn() => result?,
1636
1641
_ = token.cancelled() => {
1637
1642
tracing::info!("Shutdown signal received, stopping accept loop");
1638
1643
break;
1639
1644
}
1640
1645
}
1641
1646
} else {
1642
- listener.accept().await?
1647
+ accept_fn().await?
1643
1648
};
1644
- tracing::debug!("Accepted connection from {}", peer);
1649
+ tracing::debug!("Accepted connection from {:?}", peer);
1645
1650
1646
- // --- Normal HTTP via hyper -----------------------------------------
1647
1651
let chain = chain.clone();
1648
1652
let static_dir = static_dir.clone();
1649
1653
let static_mounts = static_mounts.clone();
1650
1654
#[cfg(feature = "wasm")]
1651
1655
let wasm_middleware = wasm_middleware.clone();
1652
- let peer_addr = peer;
1653
-
1654
1656
#[cfg(feature = "ws")]
1655
1657
let ws_handler = ws_handler.clone();
1656
-
1657
1658
let token_clone = shutdown.clone();
1658
1659
let conn_semaphore = conn_semaphore.clone();
1659
1660
connections.spawn(serve_connection(
1660
1661
stream,
1661
- Some(peer_addr),
1662
+ peer,
1662
1663
chain,
1663
1664
static_dir,
1664
1665
static_mounts,
1689
1690
Ok(())
1690
1691
}
1691
1692
1693
+async fn serve_http(
1694
+ listener: TcpListener,
1695
+ chain: Arc<MiddlewareChain>,
1696
+ static_dir: Option<StaticDir>,
1697
+ static_mounts: Vec<StaticMount>,
1698
+ metrics: Metrics,
1699
+ shutdown: Option<CancellationToken>,
1700
+ shutdown_timeout: std::time::Duration,
1701
+ #[cfg(feature = "wasm")] wasm_middleware: Option<Arc<crate::wasm::WasmEngine>>,
1702
+ #[cfg(feature = "ws")] ws_handler: Option<WsHandler>,
1703
+) -> Result<()> {
1704
+ let local_addr = listener.local_addr()?;
1705
+ tracing::info!("Listening on {}", local_addr);
1706
+
1707
+ let listener = Arc::new(listener);
1708
+ serve_generic(
1709
+ move || {
1710
+ let listener = listener.clone();
1711
+ Box::pin(async move {
1712
+ let (stream, peer) = listener.accept().await?;
1713
+ Ok((stream, Some(peer)))
1714
+ })
1715
+ },
1716
+ chain,
1717
+ static_dir,
1718
+ static_mounts,
1719
+ metrics,
1720
+ shutdown,
1721
+ shutdown_timeout,
1722
+ #[cfg(feature = "wasm")]
1723
+ wasm_middleware,
1724
+ #[cfg(feature = "ws")]
1725
+ ws_handler,
1726
+ )
1727
+ .await
1728
+}
1729
+
1692
1730
/// Bind a Unix domain socket, removing any stale socket file left from a
1693
1731
/// previous run (a common footgun — `bind` fails with EADDRINUSE on an existing
1694
1732
/// socket path). The socket file is unlinked when the listener is dropped.
1742
1780
#[cfg(feature = "wasm")] wasm_middleware: Option<Arc<crate::wasm::WasmEngine>>,
1743
1781
#[cfg(feature = "ws")] ws_handler: Option<WsHandler>,
1744
1782
) -> Result<()> {
1745
- let mut connections = tokio::task::JoinSet::new();
1746
- let conn_semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(max_connections()));
1747
-
1748
- loop {
1749
- let token = shutdown.as_ref().cloned();
1750
-
1751
- let (stream, _peer) = if let Some(token) = &token {
1752
- tokio::select! {
1753
- result = listener.accept() => result?,
1754
- _ = token.cancelled() => {
1755
- tracing::info!("Shutdown signal received, stopping accept loop");
1756
- break;
1757
- }
1758
- }
1759
- } else {
1760
- listener.accept().await?
1761
- };
1762
-
1763
- let chain = chain.clone();
1764
- let static_dir = static_dir.clone();
1765
- let static_mounts = static_mounts.clone();
1783
+ tracing::info!("Listening on Unix socket");
1784
+
1785
+ let listener = Arc::new(listener);
1786
+ serve_generic(
1787
+ move || {
1788
+ let listener = listener.clone();
1789
+ Box::pin(async move {
1790
+ let (stream, _peer) = listener.accept().await?;
1791
+ Ok((stream, None))
1792
+ })
1793
+ },
1794
+ chain,
1795
+ static_dir,
1796
+ static_mounts,
1797
+ metrics,
1798
+ shutdown,
1799
+ shutdown_timeout,
1766
1800
#[cfg(feature = "wasm")]
1767
- let wasm_middleware = wasm_middleware.clone();
1768
-
1801
+ wasm_middleware,
1769
1802
#[cfg(feature = "ws")]
1770
- let ws_handler = ws_handler.clone();
1771
-
1772
- let conn_semaphore = conn_semaphore.clone();
1773
- connections.spawn(serve_connection(
1774
- stream,
1775
- None,
1776
- chain,
1777
- static_dir,
1778
- static_mounts,
1779
- conn_semaphore,
1780
- metrics.clone(),
1781
- shutdown.clone(),
1782
- #[cfg(feature = "wasm")]
1783
- wasm_middleware,
1784
- #[cfg(feature = "ws")]
1785
- ws_handler,
1786
- ));
1787
- }
1788
-
1789
- if shutdown.is_some() {
1790
- tracing::info!("Waiting for {} active connections to drain...", connections.len());
1791
- tokio::select! {
1792
- _ = async { while connections.join_next().await.is_some() {} } => {
1793
- tracing::debug!("all connections closed gracefully");
1794
- }
1795
- _ = tokio::time::sleep(shutdown_timeout) => {
1796
- tracing::warn!("Graceful shutdown timeout exceeded. Dropping remaining connections.");
1797
- }
1798
- }
1799
- } else {
1800
- while connections.join_next().await.is_some() {}
1801
- }
1802
-
1803
- Ok(())
1803
+ ws_handler,
1804
+ )
1805
+ .await
1804
1806
}
1805
1807
1806
1808
// ---------------------------------------------------------------------------
2058
2060
// Bind a Unix socket, serve a trivial handler over it, and confirm a raw
2059
2061
// HTTP/1.1 request over the socket gets a 200 response. Exercises the
2060
2062
// shared `serve_connection` path through `serve_unix`.
2061
- #[cfg(unix)]
2063
+ #[cfg(all(unix, not(miri)))]
2062
2064
#[tokio::test]
2063
2065
async fn test_unix_socket_serves_http() {
2064
2066
use tokio::io::{AsyncReadExt, AsyncWriteExt};
2065
2067
▼crates/justapi-core/tests/edge_cases.rs
+2-2
156
156
157
157
// Slow check (simulated)
158
158
registry.register_fn("slow", || async {
159
- tokio::time::sleep(std::time::Duration::from_secs(10)).await;
159
+ tokio::time::sleep(std::time::Duration::from_millis(200)).await;
160
160
HealthStatus::Healthy
161
161
});
162
162
163
- let report = registry.check_all().await;
163
+ let report = registry.check_all_with_timeout(std::time::Duration::from_millis(50)).await;
164
164
165
165
// Fast check should pass
166
166
let fast = report.components.iter().find(|c| c.name == "fast");
167
167