1pub mod mcp;
13pub mod peer_persister;
14pub mod peer_registry;
15pub mod postgres;
16pub mod router;
17pub mod server_ownership;
18pub mod telemetry;
19pub mod ws_handler;
20pub mod ws_timing;
21
22use std::{
24 collections::HashMap,
25 net::SocketAddr,
26 sync::{
27 Arc, RwLock,
28 atomic::{AtomicBool, Ordering},
29 },
30 time::Duration,
31};
32
33use futures_util::StreamExt;
34pub use myko::server::*;
35use myko::{
36 client::MykoClient, command::CommandContext, request::RequestContext, saga::SagaRegistration,
37 search::SearchIndex, store::StoreRegistry, wire::MEvent,
38};
39pub use peer_persister::PeerPersister;
40pub use server_ownership::ServerOwnershipManager;
41use uuid::Uuid;
42
43use crate::postgres::{
44 CellPostgresConsumer, CellPostgresProducer, PostgresConfig, PostgresHistoryReplayProvider,
45 PostgresHistoryStore, PostgresProducerHandle,
46};
47
48#[derive(Clone)]
50pub struct CellServerConfig {
51 pub bind_addr: SocketAddr,
53 pub tcp_nodelay: bool,
59 pub postgres: Option<PostgresConfig>,
61 pub host_id: Option<Uuid>,
63 pub peer_registry: Option<peer_registry::PeerRegistryConfig>,
65 pub default_persister: Option<Arc<dyn Persister>>,
67 pub persister_overrides: HashMap<String, Arc<dyn Persister>>,
69 pub peer_clients: Option<Arc<dashmap::DashMap<Arc<str>, Arc<MykoClient>>>>,
73}
74
75#[derive(Default)]
77pub struct CellServerBuilder {
78 bind_addr: Option<SocketAddr>,
79 tcp_nodelay: Option<bool>,
80 host_id: Option<Uuid>,
81 postgres: Option<PostgresConfig>,
82 peer_registry: Option<peer_registry::PeerRegistryConfig>,
83 default_persister: Option<Arc<dyn Persister>>,
84 persister_overrides: HashMap<String, Arc<dyn Persister>>,
85 peer_clients: Option<Arc<dashmap::DashMap<Arc<str>, Arc<MykoClient>>>>,
89 after_init: Option<AfterInitCallback>,
90 server_info: Option<mcp::dispatch::ServerInfo>,
94}
95
96type AfterInitCallback = Box<dyn FnOnce(&CellServer) + Send>;
97
98impl CellServerBuilder {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn with_bind_addr(mut self, addr: SocketAddr) -> Self {
106 self.bind_addr = Some(addr);
107 self
108 }
109
110 pub fn with_tcp_nodelay(mut self, enabled: bool) -> Self {
115 self.tcp_nodelay = Some(enabled);
116 self
117 }
118
119 pub fn with_host_id(mut self, id: Uuid) -> Self {
121 self.host_id = Some(id);
122 self
123 }
124
125 pub fn with_postgres(mut self, config: PostgresConfig) -> Self {
127 self.postgres = Some(config);
128 self
129 }
130
131 pub fn with_peer_registry(mut self, config: peer_registry::PeerRegistryConfig) -> Self {
133 self.peer_registry = Some(config);
134 self
135 }
136
137 pub fn with_default_persister(mut self, persister: Arc<dyn Persister>) -> Self {
139 self.default_persister = Some(persister);
140 self
141 }
142
143 pub fn with_persister_override(
145 mut self,
146 entity_type: impl Into<String>,
147 persister: Arc<dyn Persister>,
148 ) -> Self {
149 self.persister_overrides
150 .insert(entity_type.into(), persister);
151 self
152 }
153
154 pub fn with_peer_clients(
160 mut self,
161 peer_clients: Arc<dashmap::DashMap<Arc<str>, Arc<MykoClient>>>,
162 ) -> Self {
163 self.peer_clients = Some(peer_clients);
164 self
165 }
166
167 pub fn after_init(mut self, f: impl FnOnce(&CellServer) + Send + 'static) -> Self {
171 self.after_init = Some(Box::new(f));
172 self
173 }
174
175 pub fn with_server_info(mut self, info: mcp::dispatch::ServerInfo) -> Self {
179 self.server_info = Some(info);
180 self
181 }
182
183 pub fn build(self) -> CellServer {
185 let bind_addr = self
186 .bind_addr
187 .unwrap_or_else(|| "127.0.0.1:5155".parse().unwrap());
188
189 let server_info = Arc::new(self.server_info.unwrap_or_default());
190
191 let mut server = CellServer::new(CellServerConfig {
192 bind_addr,
193 tcp_nodelay: self.tcp_nodelay.unwrap_or(true),
194 postgres: self.postgres,
195 host_id: self.host_id,
196 peer_registry: self.peer_registry,
197 default_persister: self.default_persister,
198 persister_overrides: self.persister_overrides,
199 peer_clients: self.peer_clients,
200 });
201 server.after_init = std::sync::Mutex::new(self.after_init);
202 server.server_info = server_info;
203 server
204 }
205}
206
207pub struct CellServer {
211 pub registry: Arc<StoreRegistry>,
213 pub handler_registry: Arc<HandlerRegistry>,
215 pub relationship_manager: Arc<RelationshipManager>,
217 pub postgres_producer: Option<PostgresProducerHandle>,
219 pub search_index: Arc<SearchIndex>,
221 pub persisters: Arc<PersisterRouter>,
223 pub host_id: Uuid,
225 config: CellServerConfig,
227 _postgres_producer_owner: Option<CellPostgresProducer>,
229 postgres_consumer: Option<CellPostgresConsumer>,
231 ready: Arc<AtomicBool>,
233 peer_registry_instance: RwLock<Option<peer_registry::PeerRegistry>>,
235 peer_clients: Arc<dashmap::DashMap<Arc<str>, Arc<MykoClient>>>,
237 after_init: std::sync::Mutex<Option<AfterInitCallback>>,
239 server_info: Arc<mcp::dispatch::ServerInfo>,
243 saga_event_tx: flume::Sender<MEvent>,
245 saga_event_rx: std::sync::Mutex<Option<flume::Receiver<MEvent>>>,
247 saga_tasks: std::sync::Mutex<Vec<tokio::task::JoinHandle<()>>>,
249 _server_ownership_guard: std::sync::Mutex<Option<hyphae::SubscriptionGuard>>,
251 ctx_cache: std::sync::OnceLock<CellServerCtx>,
260}
261
262impl CellServer {
263 pub fn builder() -> CellServerBuilder {
265 CellServerBuilder::new()
266 }
267
268 pub fn new(config: CellServerConfig) -> Self {
270 let host_id = config.host_id.unwrap_or_else(Uuid::new_v4);
271 let registry = Arc::new(StoreRegistry::new());
272 let handler_registry = Arc::new(HandlerRegistry::new());
273 let relationship_manager = Arc::new(RelationshipManager::new());
274
275 init_client_registry();
277
278 let (saga_event_tx, saga_event_rx) = flume::unbounded::<MEvent>();
279 let (postgres_producer_owner, postgres_producer, postgres_consumer) =
280 if let Some(ref postgres_config) = config.postgres {
281 match CellPostgresProducer::new(postgres_config, host_id) {
282 Ok(producer) => {
283 let handle = producer.handle();
284 let consumer = match CellPostgresConsumer::start(
285 postgres_config,
286 host_id,
287 handler_registry.clone(),
288 registry.clone(),
289 ) {
290 Ok(c) => Some(c),
291 Err(e) => {
292 tracing::error!("Failed to start Postgres consumer: {}", e);
293 None
294 }
295 };
296 (Some(producer), Some(handle), consumer)
297 }
298 Err(e) => {
299 tracing::error!("Failed to create Postgres producer: {}", e);
300 (None, None, None)
301 }
302 }
303 } else {
304 (None, None, None)
305 };
306
307 let ready = Arc::new(AtomicBool::new(postgres_consumer.is_none()));
309
310 let search_index = Arc::new(SearchIndex::new());
312
313 let mut persister_router = PersisterRouter::default();
318 if let Some(default_persister) = config.default_persister.clone() {
319 persister_router.set_default(Some(default_persister));
320 } else if let Some(handle) = postgres_producer.clone() {
321 persister_router.set_default(Some(Arc::new(handle) as Arc<dyn Persister>));
322 }
323 for (entity_type, persister) in &config.persister_overrides {
324 persister_router.set_override(entity_type.clone(), persister.clone());
325 }
326 let persisters = Arc::new(persister_router);
327
328 let peer_clients = config
329 .peer_clients
330 .clone()
331 .unwrap_or_else(|| Arc::new(dashmap::DashMap::new()));
332
333 Self {
334 registry,
335 handler_registry,
336 relationship_manager,
337 postgres_producer,
338 search_index,
339 persisters,
340 host_id,
341 config,
342 _postgres_producer_owner: postgres_producer_owner,
343 postgres_consumer,
344 ready,
345 peer_registry_instance: RwLock::new(None),
346 peer_clients,
347 after_init: std::sync::Mutex::new(None),
348 server_info: Arc::new(mcp::dispatch::ServerInfo::default()),
349 saga_event_tx,
350 saga_event_rx: std::sync::Mutex::new(Some(saga_event_rx)),
351 saga_tasks: std::sync::Mutex::new(Vec::new()),
352 _server_ownership_guard: std::sync::Mutex::new(None),
353 ctx_cache: std::sync::OnceLock::new(),
354 }
355 }
356
357 pub fn start_peer_registry(&self, config: Option<peer_registry::PeerRegistryConfig>) {
359 let peer_config = config.or_else(|| self.config.peer_registry.clone());
360
361 if let Some(peer_config) = peer_config {
362 tracing::info!("Starting peer registry");
363 let pr = peer_registry::PeerRegistry::new(self.ctx(), peer_config);
364 *self.peer_registry_instance.write().unwrap() = Some(pr);
365 }
366 }
367
368 pub fn has_peer_registry(&self) -> bool {
370 self.peer_registry_instance.read().unwrap().is_some()
371 }
372
373 pub fn registry(&self) -> Arc<StoreRegistry> {
375 self.registry.clone()
376 }
377
378 pub fn handler_registry(&self) -> Arc<HandlerRegistry> {
380 self.handler_registry.clone()
381 }
382
383 pub fn server_info(&self) -> Arc<mcp::dispatch::ServerInfo> {
386 self.server_info.clone()
387 }
388
389 pub fn ctx(&self) -> CellServerCtx {
399 self.ctx_cache
400 .get_or_init(|| {
401 let history_replay: Option<Arc<dyn myko::server::HistoryReplayProvider>> =
402 self.config.postgres.as_ref().map(|pg| {
403 Arc::new(PostgresHistoryReplayProvider::new(pg.clone()))
404 as Arc<dyn myko::server::HistoryReplayProvider>
405 });
406 CellServerCtx::new(
407 self.host_id,
408 self.registry.clone(),
409 self.handler_registry.clone(),
410 self.relationship_manager.clone(),
411 self.persisters.clone(),
412 self.search_index.clone(),
413 self.peer_clients.clone(),
414 Some(self.saga_event_tx.clone()),
415 history_replay,
416 )
417 })
418 .clone()
419 }
420
421 fn start_saga_runtime(&self) {
422 let registrations: Vec<_> = inventory::iter::<SagaRegistration>().collect();
423 if registrations.is_empty() {
424 return;
425 }
426 let Some(rx) = self
427 .saga_event_rx
428 .lock()
429 .expect("saga_event_rx mutex poisoned")
430 .take()
431 else {
432 return;
433 };
434
435 tracing::info!("Starting saga runtime with {} saga(s)", registrations.len());
436
437 struct SagaChannel {
440 tx: flume::Sender<MEvent>,
441 entity_type: &'static str,
442 change_type: myko::event::MEventType,
443 }
444 let mut saga_channels: Vec<SagaChannel> = Vec::new();
445
446 for registration in registrations {
447 let saga = (registration.create)();
448 let saga_name = saga.name().to_string();
449 let (saga_tx, saga_rx) = flume::unbounded::<MEvent>();
450 saga_channels.push(SagaChannel {
451 tx: saga_tx,
452 entity_type: registration.event_entity_type,
453 change_type: registration.event_change_type,
454 });
455 let events: myko::saga::EventStream = Box::pin(futures_util::stream::unfold(
456 saga_rx,
457 move |saga_rx| async move {
458 saga_rx
459 .recv_async()
460 .await
461 .ok()
462 .map(|event| (event, saga_rx))
463 },
464 ));
465
466 let saga_ctx = Arc::new(myko::saga::SagaContext::with_event_sink(
467 self.host_id,
468 self.registry.clone(),
469 self.saga_event_tx.clone(),
470 ));
471 let mut command_stream = saga.build_boxed(events, saga_ctx);
472
473 let host_id = self.host_id;
474 let registry = self.registry.clone();
475 let handler_registry = self.handler_registry.clone();
476 let relationship_manager = self.relationship_manager.clone();
477 let persisters = self.persisters.clone();
478 let search_index = self.search_index.clone();
479 let peer_clients = self.peer_clients.clone();
480 let saga_event_tx = self.saga_event_tx.clone();
481
482 let handle = tokio::spawn(async move {
483 while let Some(command) = command_stream.next().await {
484 let command_name = command.command_name();
485 tracing::debug!("Saga {} executing command {}", saga_name, command_name);
486 let req = Arc::new(RequestContext::internal(
487 Arc::from(Uuid::new_v4().to_string()),
488 host_id,
489 &format!("saga:{saga_name}"),
490 ));
491
492 let cmd_ctx = CommandContext::new(
493 Arc::from(command_name),
494 req,
495 Arc::new(CellServerCtx::new(
496 host_id,
497 registry.clone(),
498 handler_registry.clone(),
499 relationship_manager.clone(),
500 persisters.clone(),
501 search_index.clone(),
502 peer_clients.clone(),
503 Some(saga_event_tx.clone()),
504 None,
505 )),
506 );
507
508 if let Err(err) = command.execute_boxed(cmd_ctx) {
509 tracing::error!(
510 "Saga {} command {} failed: {}",
511 saga_name,
512 command_name,
513 err.message
514 );
515 }
516 }
517 });
518
519 self.saga_tasks
520 .lock()
521 .expect("saga_tasks mutex poisoned")
522 .push(handle);
523 }
524
525 let dispatcher = tokio::spawn(async move {
528 while let Ok(event) = rx.recv_async().await {
529 for ch in &saga_channels {
530 if event.item_type == ch.entity_type && event.change_type == ch.change_type {
531 let _ = ch.tx.send(event.clone());
532 }
533 }
534 }
535 });
536 self.saga_tasks
537 .lock()
538 .expect("saga_tasks mutex poisoned")
539 .push(dispatcher);
540 }
541
542 pub fn postgres_history_store(&self) -> Result<Option<PostgresHistoryStore>, String> {
544 self.config
545 .postgres
546 .clone()
547 .map(PostgresHistoryStore::new)
548 .transpose()
549 }
550
551 pub fn init_postgres_and_wait(&self, timeout: Duration) -> Result<(), String> {
553 if self.config.postgres.is_some() && self.postgres_consumer.is_none() {
554 return Err(
555 "Postgres is configured but the Postgres consumer is not running".to_string(),
556 );
557 }
558
559 if let Some(ref consumer) = self.postgres_consumer {
560 consumer.wait_until_caught_up(timeout)?;
561 self.ready.store(true, Ordering::SeqCst);
562 }
563 Ok(())
564 }
565
566 pub fn establish_relations(&self) {
568 if let Err(e) = self.relationship_manager.establish_relations(&self.ctx()) {
569 tracing::error!("Failed to establish relations: {e}");
570 }
571 }
572
573 pub fn is_ready(&self) -> bool {
575 if let Some(ref consumer) = self.postgres_consumer {
576 if consumer.is_caught_up() {
577 self.ready.store(true, Ordering::SeqCst);
578 return true;
579 }
580 return false;
581 }
582 true
583 }
584
585 pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
587 use tokio::net::TcpListener;
588
589 let entity_types: Vec<&str> = self
591 .handler_registry
592 .entity_types()
593 .map(|t| t.as_ref())
594 .collect();
595 self.persisters
596 .startup_healthcheck(&entity_types)
597 .map_err(|reason| format!("Persister startup healthcheck failed: {reason}"))?;
598
599 if self.config.postgres.is_some() && self.postgres_consumer.is_none() {
600 return Err("Postgres is configured but the Postgres consumer failed to start".into());
601 }
602
603 if self.postgres_consumer.is_some() {
605 tracing::info!("Waiting for Postgres event consumer to catch up...");
606 let timeout = std::time::Duration::from_secs(300);
607 self.init_postgres_and_wait(timeout)
608 .map_err(|reason| format!("Postgres startup catch-up failed: {reason}"))?;
609 tracing::info!("Postgres caught up, ready to accept connections");
610 }
611
612 tracing::info!("Building search index...");
614 self.search_index.build_from_registry(&self.registry);
615
616 tracing::info!("Establishing relations...");
618 self.establish_relations();
619
620 tracing::info!("Checking server-owned item ownership...");
622 if let Err(e) = ServerOwnershipManager::claim_orphaned(&self.ctx()) {
623 tracing::error!("Failed to claim orphaned server-owned items: {}", e);
624 }
625 let ownership_guard = ServerOwnershipManager::watch_peer_deaths(&self.ctx());
626 *self
627 ._server_ownership_guard
628 .lock()
629 .expect("server_ownership_guard mutex poisoned") = Some(ownership_guard);
630
631 if let Some(hook) = self
644 .after_init
645 .lock()
646 .expect("after_init mutex poisoned")
647 .take()
648 {
649 hook(self);
650 }
651
652 self.start_saga_runtime();
653
654 crate::ws_timing::start_periodic_logger();
658
659 myko::server::report_cache_stats::start_periodic_logger();
662
663 myko::server::entity_set_stats::start_periodic_logger();
666
667 myko::search::search_stats::start_periodic_logger();
670
671 crate::telemetry::register_item_count_gauge(self.registry.clone());
675
676 crate::telemetry::start_malloc_trim_probe();
680
681 let listener = TcpListener::bind(&self.config.bind_addr).await?;
686 tracing::info!("CellServer listening on {}", self.config.bind_addr);
687 tracing::info!(
688 "Myko gateway: ws://{}/myko | MCP: /myko/mcp (POST + WS + SSE)",
689 self.config.bind_addr
690 );
691
692 if self.config.peer_registry.is_some() {
694 self.start_peer_registry(None);
695 }
696
697 tracing::info!("Server started");
698 self.run_ws_accept_loop(listener).await
699 }
700
701 pub async fn run_ws_loop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
703 use tokio::net::TcpListener;
704
705 let listener = TcpListener::bind(&self.config.bind_addr).await?;
706 tracing::info!("CellServer listening on {}", self.config.bind_addr);
707 tracing::info!(
708 "Myko gateway: ws://{}/myko | MCP: /myko/mcp (POST + WS + SSE)",
709 self.config.bind_addr
710 );
711 self.run_ws_accept_loop(listener).await
712 }
713
714 async fn run_ws_accept_loop(
715 &self,
716 listener: tokio::net::TcpListener,
717 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
718 let ready = self.ready.clone();
719
720 loop {
721 let (stream, addr) = listener.accept().await?;
722
723 if self.config.tcp_nodelay
732 && let Err(e) = stream.set_nodelay(true)
733 {
734 tracing::warn!("failed to set TCP_NODELAY on connection from {addr}: {e}");
735 }
736
737 if !ready.load(Ordering::SeqCst) {
739 if self.is_ready() {
740 tracing::info!("Server is now ready to accept connections");
741 } else {
742 tracing::warn!(
743 "Rejecting connection from {} - server not ready (durable backend catching up)",
744 addr
745 );
746 drop(stream);
747 continue;
748 }
749 }
750
751 tracing::debug!("New connection from {}", addr);
752
753 let ctx = Arc::new(self.ctx());
754 let server_info = self.server_info.clone();
755
756 tokio::spawn(async move {
757 if let Err(e) = router::route_connection(stream, addr, ctx, server_info).await {
758 tracing::error!("Connection error from {}: {}", addr, e);
759 }
760 });
761 }
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768
769 #[test]
770 fn test_server_creation() {
771 let config = CellServerConfig {
772 bind_addr: "127.0.0.1:0".parse().unwrap(),
773 tcp_nodelay: true,
774 postgres: None,
775 host_id: None,
776 peer_registry: None,
777 default_persister: None,
778 persister_overrides: HashMap::new(),
779 peer_clients: None,
780 };
781 let server = CellServer::new(config);
782 assert!(Arc::strong_count(&server.registry) >= 1);
783 }
784
785 #[test]
786 fn test_server_with_host_id() {
787 let host_id = Uuid::new_v4();
788 let config = CellServerConfig {
789 bind_addr: "127.0.0.1:0".parse().unwrap(),
790 tcp_nodelay: true,
791 postgres: None,
792 host_id: Some(host_id),
793 peer_registry: None,
794 default_persister: None,
795 persister_overrides: HashMap::new(),
796 peer_clients: None,
797 };
798 let server = CellServer::new(config);
799 assert_eq!(server.host_id, host_id);
800 }
801
802 #[test]
803 fn ctx_is_memoized_and_shares_caches() {
804 let config = CellServerConfig {
810 bind_addr: "127.0.0.1:0".parse().unwrap(),
811 tcp_nodelay: true,
812 postgres: None,
813 host_id: None,
814 peer_registry: None,
815 default_persister: None,
816 persister_overrides: HashMap::new(),
817 peer_clients: None,
818 };
819 let server = CellServer::new(config);
820 let ctx1 = server.ctx();
821 let ctx2 = server.ctx();
822
823 let req = Arc::new(RequestContext::internal(
824 Arc::from("test"),
825 server.host_id,
826 "ctx_sharing_test",
827 ));
828 let _held = ctx1.query_map(myko::entities::client::GetAllClients {}, req);
830
831 assert!(
832 ctx1.query_cache_len() >= 1,
833 "querying through ctx1 should populate the shared query cache"
834 );
835 assert_eq!(
836 ctx2.query_cache_len(),
837 ctx1.query_cache_len(),
838 "both ctx() calls must share one query cache"
839 );
840 }
841}