feox_server/server.rs
1use crate::client_registry::ClientRegistry;
2use crate::pubsub::{handle_pubsub_operation, GlobalRegistry, ThreadLocalPubSub};
3use crate::{config::Config, error::Result, network::Connection};
4use feoxdb::FeoxStore;
5use std::net::TcpListener;
6use std::os::fd::{AsRawFd, RawFd};
7use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
8use std::sync::Arc;
9use std::thread;
10use tracing::{debug, error, info};
11
12/// High-performance Redis-compatible server
13pub struct Server {
14 config: Config,
15 store: Arc<FeoxStore>,
16 shutdown: AtomicBool,
17 active_connections: AtomicUsize,
18 pubsub_registry: Arc<GlobalRegistry>,
19 client_registry: Arc<ClientRegistry>,
20}
21
22impl Server {
23 /// Create a new server with the given configuration
24 pub fn new(config: Config) -> Result<Self> {
25 config.validate()?;
26
27 // Create a single shared FeoxStore instance
28 let store = if let Some(ref data_path) = config.data_path {
29 let mut builder = FeoxStore::builder()
30 .device_path(data_path.clone())
31 .max_memory(config.max_memory_per_shard.unwrap_or(1024 * 1024 * 1024))
32 .enable_ttl(config.enable_ttl);
33
34 // Set file size if configured
35 if let Some(file_size) = config.file_size {
36 builder = builder.file_size(file_size);
37 }
38
39 Arc::new(builder.build()?)
40 } else {
41 Arc::new(
42 FeoxStore::builder()
43 .max_memory(config.max_memory_per_shard.unwrap_or(1024 * 1024 * 1024))
44 .enable_ttl(config.enable_ttl)
45 .build()?,
46 )
47 };
48
49 let (pubsub_registry, _receivers) = GlobalRegistry::new(config.threads);
50 let client_registry = Arc::new(ClientRegistry::new());
51
52 Ok(Self {
53 config,
54 store,
55 shutdown: AtomicBool::new(false),
56 active_connections: AtomicUsize::new(0),
57 pubsub_registry,
58 client_registry,
59 })
60 }
61
62 /// Run the server, spawning worker threads
63 ///
64 /// This method blocks until the server is shut down.
65 pub fn run(self: Arc<Self>) -> Result<()> {
66 // Create TCP listener
67 let listener =
68 TcpListener::bind(format!("{}:{}", self.config.bind_addr, self.config.port))?;
69
70 listener.set_nonblocking(true)?;
71 let listener_fd = listener.as_raw_fd();
72
73 info!(
74 "Server listening on {}:{}",
75 self.config.bind_addr, self.config.port
76 );
77
78 // Create pub/sub receivers for each thread
79 let (_, mut pubsub_receivers) = GlobalRegistry::new(self.config.threads);
80
81 // Spawn worker threads
82 let mut handles = Vec::new();
83
84 for thread_id in 0..self.config.threads {
85 let server = Arc::clone(&self);
86 let store = Arc::clone(&self.store);
87 let pubsub_registry = Arc::clone(&self.pubsub_registry);
88 let pubsub_receiver = pubsub_receivers.remove(0);
89 let client_registry = Arc::clone(&self.client_registry);
90
91 let handle = thread::spawn(move || {
92 if let Err(e) = server.run_worker(
93 thread_id,
94 listener_fd,
95 store,
96 pubsub_registry,
97 pubsub_receiver,
98 client_registry,
99 ) {
100 error!("Worker {} failed: {}", thread_id, e);
101 }
102 });
103 handles.push(handle);
104 }
105
106 // Wait for all workers to finish
107 for handle in handles {
108 let _ = handle.join();
109 }
110
111 Ok(())
112 }
113
114 /// Signal the server to shut down gracefully
115 pub fn shutdown(&self) {
116 info!("Initiating server shutdown");
117 self.shutdown.store(true, Ordering::Release);
118 }
119
120 /// Get the number of active client connections
121 pub fn active_connections(&self) -> usize {
122 self.active_connections.load(Ordering::Acquire)
123 }
124
125 fn run_worker(
126 self: &Arc<Self>,
127 thread_id: usize,
128 listener_fd: RawFd,
129 store: Arc<FeoxStore>,
130 pubsub_registry: Arc<GlobalRegistry>,
131 pubsub_receiver: crossbeam_channel::Receiver<crate::pubsub::BroadcastMsg>,
132 client_registry: Arc<ClientRegistry>,
133 ) -> Result<()> {
134 use mio::net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream};
135 use mio::{Events, Interest, Poll, Token};
136 use std::collections::HashMap;
137 use std::io::{ErrorKind, Read, Write};
138 use std::os::fd::FromRawFd;
139
140 // Create mio Poll instance
141 let mut poll = Poll::new()?;
142 let mut events = Events::with_capacity(1024);
143
144 // Convert raw fd to mio listener
145 let std_listener = unsafe { TcpListener::from_raw_fd(listener_fd) };
146 std_listener.set_nonblocking(true)?;
147 let mut listener = MioTcpListener::from_std(std_listener);
148
149 // Register listener
150 const SERVER: Token = Token(0);
151 poll.registry()
152 .register(&mut listener, SERVER, Interest::READABLE)?;
153
154 // Connection tracking
155 let mut connections: HashMap<Token, (MioTcpStream, Connection)> = HashMap::new();
156 let mut next_token = 1usize;
157
158 // Initialize thread-local pub/sub
159 let mut pubsub_manager =
160 ThreadLocalPubSub::new(thread_id, pubsub_receiver, pubsub_registry.clone());
161
162 info!("Worker {} started", thread_id);
163
164 // Event loop
165 while !self.shutdown.load(Ordering::Acquire) {
166 // Process incoming pub/sub messages
167 let pubsub_deliveries = pubsub_manager.process_inbox();
168 for (conn_id, message) in pubsub_deliveries {
169 // Find connection by ID and queue message
170 for (_token, (stream, connection)) in connections.iter_mut() {
171 if connection.connection_id == conn_id {
172 connection.queue_pubsub_message(message);
173 connection.process_pubsub_messages();
174
175 // Write any pending data immediately
176 while let Some(data) = connection.pending_writes() {
177 let data_len = data.len();
178 match stream.write(data) {
179 Ok(n) => {
180 connection.consume_writes(n);
181 if n < data_len {
182 break;
183 }
184 }
185 Err(e) if e.kind() == ErrorKind::WouldBlock => break,
186 Err(_) => break,
187 }
188 }
189 break;
190 }
191 }
192 }
193
194 // Poll for events with 100ms timeout
195 poll.poll(&mut events, Some(std::time::Duration::from_millis(100)))?;
196
197 for event in events.iter() {
198 match event.token() {
199 SERVER => {
200 // Accept new connections
201 loop {
202 match listener.accept() {
203 Ok((mut stream, addr)) => {
204 debug!("New connection from {:?}", addr);
205
206 // Configure socket
207 stream.set_nodelay(self.config.tcp_nodelay)?;
208
209 let token = Token(next_token);
210 next_token += 1;
211
212 // Register for read events
213 poll.registry().register(
214 &mut stream,
215 token,
216 Interest::READABLE,
217 )?;
218
219 let mut connection = Connection::new_with_addr(
220 0, // fd not used in this path
221 self.config.connection_buffer_size,
222 Arc::clone(&store),
223 &self.config, // Pass config here
224 Some(addr),
225 );
226
227 // Set client registry for CLIENT command support
228 connection.set_client_registry(Arc::clone(&client_registry));
229
230 // Register client in registry
231 client_registry.register(&connection, thread_id);
232
233 connections.insert(token, (stream, connection));
234 self.active_connections.fetch_add(1, Ordering::Relaxed);
235 }
236 Err(e) if e.kind() == ErrorKind::WouldBlock => break,
237 Err(e) => {
238 error!("Error accepting connection: {}", e);
239 break;
240 }
241 }
242 }
243 }
244 token => {
245 // First, collect any deliveries that need to be made
246 let mut deliveries_to_make = Vec::new();
247
248 // Handle client connection
249 let should_close =
250 if let Some((stream, connection)) = connections.get_mut(&token) {
251 let mut should_close = false;
252
253 if event.is_readable() {
254 // Use a simple buffer (optimize with pool later if needed)
255 let mut buffer = vec![0u8; 8192];
256
257 match stream.read(&mut buffer) {
258 Ok(0) => {
259 // Connection closed
260 should_close = true;
261 }
262 Ok(n) => {
263 // Process commands inline and get pub/sub operations
264 match connection.process_read(&buffer[..n]) {
265 Ok(pubsub_ops) => {
266 // Process pub/sub operations
267 for op in pubsub_ops {
268 let deliveries = handle_pubsub_operation(
269 &mut pubsub_manager,
270 &pubsub_registry,
271 connection.connection_id,
272 op,
273 connection,
274 thread_id,
275 );
276 deliveries_to_make.extend(deliveries);
277 }
278
279 // Process any queued pub/sub messages
280 connection.process_pubsub_messages();
281
282 // Update client info in registry if needed
283 client_registry.update(connection);
284
285 // Write response immediately
286 while let Some(response_data) =
287 connection.pending_writes()
288 {
289 let response_len = response_data.len();
290 match stream.write(response_data) {
291 Ok(n) => {
292 connection.consume_writes(n);
293 if n < response_len {
294 // Partial write, would block
295 break;
296 }
297 }
298 Err(e)
299 if e.kind()
300 == ErrorKind::WouldBlock =>
301 {
302 break;
303 }
304 Err(e) => {
305 error!("Error writing: {}", e);
306 should_close = true;
307 break;
308 }
309 }
310 }
311 }
312 Err(e) => {
313 error!("Error processing read: {}", e);
314 should_close = true;
315 }
316 }
317
318 if connection.is_closed() {
319 should_close = true;
320 }
321 }
322 Err(e) if e.kind() != ErrorKind::WouldBlock => {
323 if e.kind() != ErrorKind::ConnectionReset {
324 error!("Error reading: {}", e);
325 }
326 should_close = true;
327 }
328 Err(_) => {} // WouldBlock - ignore
329 }
330 }
331
332 should_close
333 } else {
334 false
335 };
336
337 if should_close {
338 if let Some((mut stream, mut connection)) = connections.remove(&token) {
339 let _ = poll.registry().deregister(&mut stream);
340
341 // Clean up pub/sub subscriptions
342 pubsub_manager.connection_dropped(connection.connection_id);
343
344 // Unregister from client registry
345 client_registry.unregister(connection.connection_id);
346
347 connection.close();
348 self.active_connections.fetch_sub(1, Ordering::Relaxed);
349 }
350 }
351
352 // Now deliver any pub/sub messages to local connections
353 for (delivery_conn_id, msg) in deliveries_to_make {
354 for (_, (_, conn)) in connections.iter_mut() {
355 if conn.connection_id == delivery_conn_id {
356 conn.queue_pubsub_message(msg);
357 conn.process_pubsub_messages();
358 break;
359 }
360 }
361 }
362 }
363 }
364 }
365 }
366
367 // Cleanup
368 for (_, (mut stream, mut connection)) in connections {
369 let _ = poll.registry().deregister(&mut stream);
370 pubsub_manager.connection_dropped(connection.connection_id);
371 client_registry.unregister(connection.connection_id);
372 connection.close();
373 }
374
375 info!("Worker {} shutting down", thread_id);
376 Ok(())
377 }
378}