Skip to content

Commit a8d5e6f

Browse files
committed
Refactor proxy architecture and add new modules
Replaces old proxy modules with a new modular architecture: adds src/proxy.rs and protocol-specific proxy modules (http, tcp, udp), router, routing, buffers, affinity, error, and config management. Updates Cargo.toml with new dependencies and release profile. Removes legacy modules (modern_router, new_config, proxies) and updates main.rs and lib.rs to use the new system. Adds a sample YAML config and comprehensive configuration, error, and metrics handling.
1 parent e3abb49 commit a8d5e6f

22 files changed

Lines changed: 5318 additions & 1391 deletions

Cargo.toml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ path = "src/main.rs"
1515

1616
[dependencies]
1717
tokio = { version = "1.28", features = ["full"] }
18-
hyper = { version = "0.14", features = ["full"] }
18+
hyper = { version = "0.14", features = ["full", "server", "client", "http1", "http2", "tcp"] }
1919
tower = "0.4"
2020
tower-http = { version = "0.4", features = ["trace"] }
2121
serde = { version = "1.0", features = ["derive"] }
@@ -35,4 +35,25 @@ reqwest = { version = "0.12.15", features = ["json"] }
3535
async-trait = "0.1"
3636
chrono = { version = "0.4", features = ["serde"] }
3737
serde_json = "1.0"
38-
toml = "0.8"
38+
toml = "0.8"
39+
crossbeam = "0.8"
40+
crossbeam-queue = "0.3"
41+
parking_lot = "0.12"
42+
once_cell = "1.19"
43+
pin-project = "1.1"
44+
smallvec = "1.11"
45+
arrayvec = "0.7"
46+
ahash = "0.8"
47+
memmap2 = "0.9"
48+
libc = "0.2"
49+
num_cpus = "1.16"
50+
hyper-rustls = "0.24"
51+
fastrand = "2.0"
52+
53+
[profile.release]
54+
lto = "fat"
55+
codegen-units = 1
56+
panic = "abort"
57+
opt-level = 3
58+
debug = false
59+
strip = true

config_new.yml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Modern Harbr Router Configuration
2+
global:
3+
metrics:
4+
enabled: true
5+
listen_addr: "127.0.0.1:9090"
6+
path: "/metrics"
7+
interval_secs: 30
8+
9+
health_check:
10+
enabled: true
11+
listen_addr: "127.0.0.1:8080"
12+
path: "/health"
13+
14+
performance:
15+
worker_threads: 0
16+
cpu_affinity: true
17+
buffer_pool:
18+
initial_size: 1000
19+
max_size: 10000
20+
buffer_size: 8192
21+
connection_pool:
22+
max_connections_per_upstream: 100
23+
idle_timeout_secs: 300
24+
keepalive_timeout_secs: 60
25+
26+
logging:
27+
level: "info"
28+
format: "json"
29+
30+
proxy_instances:
31+
# HTTP Proxy for web traffic
32+
- name: "web_proxy"
33+
proxy_type: "http"
34+
listen_addr: "0.0.0.0:8081"
35+
upstreams:
36+
- name: "web_server_1"
37+
address: "127.0.0.1:8080"
38+
weight: 2
39+
priority: 100
40+
timeout_ms: 5000
41+
retry:
42+
max_retries: 3
43+
backoff_ms: 100
44+
max_backoff_ms: 5000
45+
health_check:
46+
interval_secs: 10
47+
timeout_secs: 3
48+
failure_threshold: 3
49+
success_threshold: 2
50+
type: "http"
51+
path: "/health"
52+
expected_status: [200]
53+
- name: "web_server_2"
54+
address: "127.0.0.1:8090"
55+
weight: 1
56+
priority: 90
57+
timeout_ms: 5000
58+
retry:
59+
max_retries: 2
60+
backoff_ms: 200
61+
max_backoff_ms: 3000
62+
config:
63+
http:
64+
max_request_size: 10485760
65+
request_timeout_secs: 30
66+
keep_alive_timeout_secs: 60
67+
connection_pool_size: 100
68+
add_headers:
69+
"X-Forwarded-Proto": "https"
70+
"X-Real-IP": "client"
71+
72+
# TCP Proxy for database connections
73+
- name: "db_proxy"
74+
proxy_type: "tcp"
75+
listen_addr: "0.0.0.0:5432"
76+
upstreams:
77+
- name: "postgres_primary"
78+
address: "127.0.0.1:5433"
79+
weight: 3
80+
priority: 100
81+
timeout_ms: 10000
82+
health_check:
83+
interval_secs: 15
84+
timeout_secs: 5
85+
failure_threshold: 3
86+
success_threshold: 2
87+
type: "tcp"
88+
- name: "postgres_replica"
89+
address: "127.0.0.1:5434"
90+
weight: 1
91+
priority: 50
92+
timeout_ms: 10000
93+
config:
94+
tcp:
95+
idle_timeout_secs: 300
96+
connection_pool_size: 50
97+
nodelay: true
98+
keepalive:
99+
enabled: true
100+
idle_secs: 600
101+
interval_secs: 60
102+
probes: 3
103+
104+
# UDP Proxy for DNS
105+
- name: "dns_proxy"
106+
proxy_type: "udp"
107+
listen_addr: "0.0.0.0:5353"
108+
upstreams:
109+
- name: "dns_server_1"
110+
address: "8.8.8.8:53"
111+
weight: 2
112+
priority: 100
113+
timeout_ms: 1000
114+
- name: "dns_server_2"
115+
address: "8.8.4.4:53"
116+
weight: 1
117+
priority: 90
118+
timeout_ms: 2000
119+
config:
120+
udp:
121+
buffer_size: 1024
122+
session_timeout_secs: 60

src/affinity.rs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//! CPU affinity management for optimal performance.
2+
//!
3+
//! This module provides cross-platform CPU affinity management to bind threads
4+
//! to specific CPU cores for better cache locality and reduced context switching.
5+
6+
use crate::{Error, Result};
7+
use once_cell::sync::Lazy;
8+
use std::sync::atomic::{AtomicUsize, Ordering};
9+
10+
/// Global CPU affinity manager
11+
static AFFINITY_MANAGER: Lazy<CpuAffinityManager> = Lazy::new(CpuAffinityManager::new);
12+
13+
/// CPU affinity manager for distributing threads across CPU cores.
14+
pub struct CpuAffinityManager {
15+
cpu_count: usize,
16+
next_cpu: AtomicUsize,
17+
}
18+
19+
impl CpuAffinityManager {
20+
/// Create a new CPU affinity manager.
21+
fn new() -> Self {
22+
let cpu_count = num_cpus::get();
23+
tracing::info!("Detected {} CPU cores", cpu_count);
24+
25+
Self {
26+
cpu_count,
27+
next_cpu: AtomicUsize::new(0),
28+
}
29+
}
30+
31+
/// Get the number of available CPU cores.
32+
pub fn cpu_count(&self) -> usize {
33+
self.cpu_count
34+
}
35+
36+
/// Get the optimal number of worker threads.
37+
/// Uses all available cores but caps at a reasonable maximum.
38+
pub fn optimal_worker_count(&self) -> usize {
39+
self.cpu_count.min(32).max(1)
40+
}
41+
42+
/// Assign the current thread to the next available CPU core.
43+
pub fn assign_current_thread(&self) -> Result<usize> {
44+
let cpu_id = self.next_cpu.fetch_add(1, Ordering::Relaxed) % self.cpu_count;
45+
set_thread_affinity(cpu_id)?;
46+
tracing::debug!("Assigned current thread to CPU core {}", cpu_id);
47+
Ok(cpu_id)
48+
}
49+
50+
/// Spawn a new thread and bind it to a specific CPU core.
51+
pub fn spawn_on_cpu<F, T>(&self, cpu_id: usize, f: F) -> std::thread::JoinHandle<T>
52+
where
53+
F: FnOnce() -> T + Send + 'static,
54+
T: Send + 'static,
55+
{
56+
let actual_cpu = cpu_id % self.cpu_count;
57+
std::thread::spawn(move || {
58+
if let Err(e) = set_thread_affinity(actual_cpu) {
59+
tracing::warn!("Failed to set CPU affinity for thread: {}", e);
60+
}
61+
f()
62+
})
63+
}
64+
}
65+
66+
/// Set CPU affinity for the current thread.
67+
#[cfg(target_os = "linux")]
68+
pub fn set_thread_affinity(cpu_id: usize) -> Result<()> {
69+
use std::mem;
70+
71+
unsafe {
72+
let mut set: libc::cpu_set_t = mem::zeroed();
73+
libc::CPU_ZERO(&mut set);
74+
libc::CPU_SET(cpu_id, &mut set);
75+
76+
let result = libc::sched_setaffinity(0, mem::size_of::<libc::cpu_set_t>(), &set);
77+
if result != 0 {
78+
let error = std::io::Error::last_os_error();
79+
return Err(Error::internal(format!("Failed to set CPU affinity: {}", error)));
80+
}
81+
}
82+
83+
Ok(())
84+
}
85+
86+
/// Set CPU affinity for the current thread (Windows implementation).
87+
#[cfg(target_os = "windows")]
88+
pub fn set_thread_affinity(cpu_id: usize) -> Result<()> {
89+
use std::ptr;
90+
91+
extern "system" {
92+
fn SetThreadAffinityMask(
93+
h_thread: *mut std::ffi::c_void,
94+
dw_thread_affinity_mask: usize,
95+
) -> usize;
96+
fn GetCurrentThread() -> *mut std::ffi::c_void;
97+
}
98+
99+
unsafe {
100+
let mask = 1usize << cpu_id;
101+
let result = SetThreadAffinityMask(GetCurrentThread(), mask);
102+
if result == 0 {
103+
return Err(Error::internal("Failed to set CPU affinity on Windows"));
104+
}
105+
}
106+
107+
Ok(())
108+
}
109+
110+
/// Set CPU affinity for the current thread (no-op on unsupported platforms).
111+
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
112+
pub fn set_thread_affinity(_cpu_id: usize) -> Result<()> {
113+
// CPU affinity not supported on this platform
114+
Ok(())
115+
}
116+
117+
/// Get the global CPU affinity manager.
118+
pub fn global_manager() -> &'static CpuAffinityManager {
119+
&AFFINITY_MANAGER
120+
}
121+
122+
/// Get the number of available CPU cores.
123+
pub fn cpu_count() -> usize {
124+
global_manager().cpu_count()
125+
}
126+
127+
/// Get the optimal number of worker threads.
128+
pub fn optimal_worker_count() -> usize {
129+
global_manager().optimal_worker_count()
130+
}
131+
132+
/// Assign the current thread to the next available CPU core.
133+
pub fn assign_current_thread() -> Result<usize> {
134+
global_manager().assign_current_thread()
135+
}
136+
137+
/// Create a Tokio runtime with CPU affinity optimization.
138+
pub fn create_optimized_runtime() -> Result<tokio::runtime::Runtime> {
139+
let worker_threads = optimal_worker_count();
140+
141+
tracing::info!("Creating Tokio runtime with {} worker threads and CPU affinity", worker_threads);
142+
143+
tokio::runtime::Builder::new_multi_thread()
144+
.worker_threads(worker_threads)
145+
.thread_name("harbr-worker")
146+
.on_thread_start(|| {
147+
if let Err(e) = assign_current_thread() {
148+
tracing::warn!("Failed to set CPU affinity for worker thread: {}", e);
149+
}
150+
})
151+
.enable_all()
152+
.build()
153+
.map_err(|e| Error::internal(format!("Failed to create Tokio runtime: {}", e)))
154+
}
155+
156+
/// Configure the current thread for optimal performance.
157+
pub fn optimize_current_thread() -> Result<()> {
158+
// Set CPU affinity
159+
assign_current_thread()?;
160+
161+
// Set thread priority on platforms that support it
162+
#[cfg(unix)]
163+
{
164+
unsafe {
165+
let result = libc::setpriority(libc::PRIO_PROCESS, 0, -10);
166+
if result != 0 {
167+
tracing::warn!("Failed to set thread priority: {}", std::io::Error::last_os_error());
168+
}
169+
}
170+
}
171+
172+
#[cfg(windows)]
173+
{
174+
extern "system" {
175+
fn SetThreadPriority(h_thread: *mut std::ffi::c_void, n_priority: i32) -> i32;
176+
fn GetCurrentThread() -> *mut std::ffi::c_void;
177+
}
178+
179+
const THREAD_PRIORITY_ABOVE_NORMAL: i32 = 1;
180+
181+
unsafe {
182+
let result = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
183+
if result == 0 {
184+
tracing::warn!("Failed to set thread priority on Windows");
185+
}
186+
}
187+
}
188+
189+
Ok(())
190+
}
191+
192+
#[cfg(test)]
193+
mod tests {
194+
use super::*;
195+
196+
#[test]
197+
fn test_cpu_count() {
198+
let count = cpu_count();
199+
assert!(count > 0, "Should detect at least one CPU core");
200+
assert!(count <= 256, "Unrealistic number of CPU cores");
201+
}
202+
203+
#[test]
204+
fn test_optimal_worker_count() {
205+
let count = optimal_worker_count();
206+
assert!(count > 0, "Should have at least one worker thread");
207+
assert!(count <= 32, "Should cap worker threads at reasonable maximum");
208+
}
209+
210+
#[test]
211+
fn test_affinity_manager() {
212+
let manager = CpuAffinityManager::new();
213+
assert_eq!(manager.cpu_count(), num_cpus::get());
214+
215+
let optimal = manager.optimal_worker_count();
216+
assert!(optimal > 0);
217+
assert!(optimal <= manager.cpu_count().min(32));
218+
}
219+
}

0 commit comments

Comments
 (0)