1use crate::contract::{new_context, Caller};
2pub use crate::contract::{CallContext, Emit, Handler};
3use crate::discovery::{record_process_matches, try_lock_exclusive, LockedFile};
4use crate::error::{ServiceError, ERROR_RESPONSE_TOO_LARGE};
5use crate::record::*;
6use crate::suite::{make_private_dir, valid_service_name, Suite, SuiteError};
7use crate::takeover_watch::{self, Bell, Wake, TAKEOVER_CHECK_INTERVAL};
8use crate::transport::{
9 self, pid_alive, probe_endpoint, try_endpoint_for, Accepted, Conn, Listener,
10 DEFAULT_MAX_PIPE_INSTANCES,
11};
12use rightkit_sidecar::{read_json_line, write_json_line, FrameError};
13use serde_json::{json, Value};
14use std::collections::HashMap;
15use std::io::{BufReader, Write};
16use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
17use std::sync::{Arc, Condvar, Mutex};
18use std::time::{Duration, Instant, SystemTime};
19
20pub const DEFAULT_FRAME_LIMIT: usize = 16 * 1024 * 1024;
21
22pub const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(120);
25
26pub const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
30
31pub const DEFAULT_RESERVED_SLOTS: usize = 2;
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum ServiceEvent {
37 Started {
38 service: String,
39 endpoint: String,
40 rank: u8,
41 },
42 ConnectionOpened {
43 conn: u64,
44 app: String,
45 },
46 ConnectionRejected {
47 app: String,
48 reason: String,
49 },
50 PeerRejected {
54 reason: String,
55 },
56 PeerUnidentified {
67 pid: Option<u32>,
68 image: Option<std::path::PathBuf>,
69 kind: crate::PeerFailureKind,
70 reason: String,
71 },
72 TableFull {
85 limit: usize,
86 reserved: usize,
87 pre_handshake: usize,
88 attached: usize,
89 peer_pid: Option<u32>,
90 },
91 Denied {
92 conn: u64,
93 app: String,
94 method: String,
95 },
96 HandoffRequested {
97 by_role: String,
98 by_rank: u8,
99 },
100 FrameTooLarge {
104 conn: u64,
105 method: String,
106 kind: &'static str,
107 limit: usize,
108 },
109 Released,
110}
111
112pub type EventFn = Arc<dyn Fn(&ServiceEvent) + Send + Sync>;
114
115pub struct ServiceConfig {
116 pub service: String,
117 pub role: String,
118 pub rank: u8,
119 pub version: String,
120 pub allowlist: Allowlist,
121 pub frame_limit: usize,
122 pub takeover_timeout: Duration,
124 pub handoff_grace: Duration,
126 pub on_event: Option<EventFn>,
127 pub known_roles: std::collections::BTreeMap<String, u8>,
131 pub endpoint: Option<String>,
134 pub private_secret: Option<crate::private::PrivateSecret>,
136}
137
138impl ServiceConfig {
139 pub fn new(service: &str, role: &str, rank: u8, version: &str, allowlist: Allowlist) -> Self {
140 Self {
141 service: service.into(),
142 role: role.into(),
143 rank,
144 version: version.into(),
145 allowlist,
146 frame_limit: DEFAULT_FRAME_LIMIT,
147 takeover_timeout: Duration::from_secs(60),
148 handoff_grace: Duration::from_secs(30),
149 on_event: None,
150 known_roles: Default::default(),
151 endpoint: None,
152 private_secret: None,
153 }
154 }
155
156 pub fn private_secret(mut self, secret: crate::private::PrivateSecret) -> Self {
161 self.private_secret = Some(secret);
162 self
163 }
164
165 pub fn with_methods(mut self, table: &crate::contract::MethodTable) -> Self {
168 self.allowlist = table.allowlist();
169 self
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub struct HostOptions {
177 max_pipe_instances: u32,
178 write_timeout: Option<Duration>,
179 max_message: usize,
180 max_connections: usize,
181 reserved_slots: usize,
182 supervisor: SupervisorPid,
183 handshake_timeout: Option<Duration>,
184}
185
186pub const DEFAULT_MAX_CONNECTIONS: usize = 64;
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191enum SupervisorPid {
192 Parent,
194 Pid(u32),
195 Nobody,
196}
197
198impl Default for HostOptions {
199 fn default() -> Self {
200 Self {
201 max_pipe_instances: DEFAULT_MAX_PIPE_INSTANCES,
202 write_timeout: Some(DEFAULT_WRITE_TIMEOUT),
203 max_message: crate::chunk::DEFAULT_MAX_MESSAGE,
204 max_connections: DEFAULT_MAX_CONNECTIONS,
205 reserved_slots: DEFAULT_RESERVED_SLOTS,
206 supervisor: SupervisorPid::Parent,
207 handshake_timeout: Some(DEFAULT_HANDSHAKE_TIMEOUT),
208 }
209 }
210}
211
212impl HostOptions {
213 pub fn new() -> Self {
214 Self::default()
215 }
216
217 #[must_use]
224 pub fn max_pipe_instances(mut self, n: u32) -> Self {
225 self.max_pipe_instances = n.clamp(1, 254);
226 self
227 }
228
229 pub fn get_max_pipe_instances(&self) -> u32 {
231 self.max_pipe_instances
232 }
233
234 #[must_use]
243 pub fn write_timeout(mut self, bound: Option<Duration>) -> Self {
244 self.write_timeout = bound.map(|d| d.max(Duration::from_millis(1)));
245 self
246 }
247
248 pub fn get_write_timeout(&self) -> Option<Duration> {
250 self.write_timeout
251 }
252
253 #[must_use]
263 pub fn max_message(mut self, bytes: usize) -> Self {
264 self.max_message = bytes;
265 self
266 }
267
268 pub fn get_max_message(&self) -> usize {
270 self.max_message
271 }
272
273 #[must_use]
280 pub fn max_connections(mut self, n: usize) -> Self {
281 self.max_connections = n.max(1);
282 self
283 }
284
285 pub fn get_max_connections(&self) -> usize {
287 self.max_connections
288 }
289
290 #[must_use]
300 pub fn reserved_slots(mut self, n: usize) -> Self {
301 self.reserved_slots = n;
302 self
303 }
304
305 pub fn get_reserved_slots(&self) -> usize {
307 self.reserved_slots
308 }
309
310 #[must_use]
315 pub fn supervisor_pid(mut self, pid: Option<u32>) -> Self {
316 self.supervisor = match pid {
317 Some(pid) => SupervisorPid::Pid(pid),
318 None => SupervisorPid::Nobody,
319 };
320 self
321 }
322
323 pub fn get_supervisor_pid(&self) -> Option<u32> {
326 match self.supervisor {
327 SupervisorPid::Pid(pid) => Some(pid),
328 SupervisorPid::Nobody => None,
329 #[cfg(unix)]
330 SupervisorPid::Parent => Some(std::os::unix::process::parent_id()),
331 #[cfg(not(unix))]
332 SupervisorPid::Parent => None,
333 }
334 }
335
336 #[must_use]
346 pub fn handshake_timeout(mut self, bound: Option<Duration>) -> Self {
347 self.handshake_timeout = bound.map(|d| d.max(Duration::from_millis(1)));
348 self
349 }
350
351 pub fn get_handshake_timeout(&self) -> Option<Duration> {
353 self.handshake_timeout
354 }
355}
356
357#[derive(Debug)]
358pub enum HostError {
359 Held(ServiceRecord),
361 TakeoverTimeout,
363 InvalidName(String),
364 Suite(SuiteError),
365 Io(std::io::Error),
366 Endpoint(crate::transport::EndpointError),
368}
369impl std::fmt::Display for HostError {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 match self {
372 HostError::Held(r) => write!(
373 f,
374 "{} is already hosted by {} (rank {}, pid {})",
375 r.service, r.role, r.rank, r.pid
376 ),
377 HostError::TakeoverTimeout => write!(f, "incumbent host did not release in time"),
378 HostError::InvalidName(s) => write!(f, "invalid service name {s:?}"),
379 HostError::Suite(e) => write!(f, "{e}"),
380 HostError::Io(e) => write!(f, "{e}"),
381 HostError::Endpoint(e) => write!(f, "{e}"),
382 }
383 }
384}
385impl std::error::Error for HostError {}
386impl From<std::io::Error> for HostError {
387 fn from(e: std::io::Error) -> Self {
388 HostError::Io(e)
389 }
390}
391impl From<SuiteError> for HostError {
392 fn from(e: SuiteError) -> Self {
393 HostError::Suite(e)
394 }
395}
396
397fn resolve_rank(cfg: &ServiceConfig, role: &str, rank: u8) -> u8 {
398 crate::discovery::resolve_rank(role, rank, &cfg.known_roles).unwrap_or(RANK_UNKNOWN)
399}
400
401struct Shared {
402 suite: Suite,
403 cfg: ServiceConfig,
404 handler: Arc<dyn Handler>,
405 listener: Listener,
406 election: Mutex<Option<LockedFile>>,
408 record: ServiceRecord,
409 stop: AtomicBool,
410 handoff: Arc<AtomicBool>,
411 released: Mutex<bool>,
412 released_cv: Condvar,
413 inflight: AtomicUsize,
414 next_conn: AtomicU64,
415 conns: Mutex<HashMap<u64, Arc<dyn Fn() + Send + Sync>>>,
416 bell: Arc<Bell>,
418 watch: WatchTuning,
419 write_timeout: Option<Duration>,
420 max_message: usize,
421 max_connections: usize,
422 live_conns: Arc<AtomicUsize>,
424 reserved_slots: usize,
426 supervisor: Option<u32>,
427 reserved_conns: Arc<AtomicUsize>,
429 pre_handshake: Arc<AtomicUsize>,
431 handshake_timeout: Option<Duration>,
432 fails: Mutex<crate::fail_limit::FailLimiter>,
434 metrics: crate::metrics::Metrics,
436}
437
438struct ForgetFails {
440 s: Arc<Shared>,
441 conn: u64,
442}
443
444impl Drop for ForgetFails {
445 fn drop(&mut self) {
446 self.s
447 .fails
448 .lock()
449 .unwrap_or_else(|e| e.into_inner())
450 .forget(self.conn, Instant::now());
451 }
452}
453
454struct ConnSlot(Arc<AtomicUsize>);
456
457impl Drop for ConnSlot {
458 fn drop(&mut self) {
459 self.0.fetch_sub(1, Ordering::SeqCst);
460 }
461}
462
463struct PreHandshake {
466 count: Arc<AtomicUsize>,
467 pending: bool,
468}
469
470impl PreHandshake {
471 fn enter(count: &Arc<AtomicUsize>) -> Self {
472 count.fetch_add(1, Ordering::SeqCst);
473 Self {
474 count: count.clone(),
475 pending: true,
476 }
477 }
478
479 fn settle(&mut self) {
481 if std::mem::take(&mut self.pending) {
482 self.count.fetch_sub(1, Ordering::SeqCst);
483 }
484 }
485}
486
487impl Drop for PreHandshake {
488 fn drop(&mut self) {
489 self.settle();
490 }
491}
492
493struct DeadlineWatch {
501 state: Mutex<WatchState>,
502 cv: Condvar,
503}
504
505struct WatchState {
506 until: Option<Instant>,
508 done: bool,
509}
510
511struct FinishOnDrop(Arc<DeadlineWatch>);
512impl Drop for FinishOnDrop {
513 fn drop(&mut self) {
514 self.0.finish();
515 }
516}
517
518impl DeadlineWatch {
519 fn start(expired: Arc<dyn Fn() + Send + Sync>) -> Arc<DeadlineWatch> {
520 let w = Arc::new(DeadlineWatch {
521 state: Mutex::new(WatchState {
522 until: None,
523 done: false,
524 }),
525 cv: Condvar::new(),
526 });
527 let watcher = w.clone();
528 std::thread::spawn(move || {
529 let mut g = watcher.state.lock().unwrap_or_else(|e| e.into_inner());
530 loop {
531 if g.done {
532 return;
533 }
534 match g.until {
535 None => g = watcher.cv.wait(g).unwrap_or_else(|e| e.into_inner()),
536 Some(t) => {
537 let left = t.saturating_duration_since(Instant::now());
538 if left.is_zero() {
539 g.until = None;
540 drop(g);
541 expired();
542 return;
543 }
544 g = watcher
545 .cv
546 .wait_timeout(g, left)
547 .unwrap_or_else(|e| e.into_inner())
548 .0;
549 }
550 }
551 }
552 });
553 w
554 }
555 fn set(&self, until: Option<Instant>) {
556 self.state.lock().unwrap_or_else(|e| e.into_inner()).until = until;
557 self.cv.notify_all();
558 }
559 fn finish(&self) {
560 self.state.lock().unwrap_or_else(|e| e.into_inner()).done = true;
561 self.cv.notify_all();
562 }
563}
564
565#[derive(Clone, Copy)]
568struct WatchTuning {
569 interval: Duration,
571 alive: fn(u32) -> bool,
573}
574
575impl WatchTuning {
576 const PRODUCTION: WatchTuning = WatchTuning {
577 interval: TAKEOVER_CHECK_INTERVAL,
578 alive: pid_alive,
579 };
580}
581
582impl Shared {
583 fn event(&self, e: ServiceEvent) {
584 if let Some(f) = &self.cfg.on_event {
585 f(&e);
586 }
587 }
588
589 fn release(&self) {
590 {
591 let mut r = self.released.lock().unwrap_or_else(|e| e.into_inner());
592 if *r {
593 return;
594 }
595 *r = true;
596 }
597 self.stop.store(true, Ordering::SeqCst);
598 self.bell.stop();
600 if read_record(&self.suite, &self.cfg.service).is_some_and(|r| r.pid == self.record.pid) {
602 let _ = std::fs::remove_file(record_path(&self.suite, &self.cfg.service));
603 }
604 self.listener.wake();
605 self.listener.cleanup();
606 for (_, close) in self.conns.lock().unwrap_or_else(|e| e.into_inner()).drain() {
607 close();
608 }
609 self.election
610 .lock()
611 .unwrap_or_else(|e| e.into_inner())
612 .take();
613 self.event(ServiceEvent::Released);
614 self.released_cv.notify_all();
615 }
616}
617
618#[derive(Debug, Clone, Copy, PartialEq)]
620pub struct ServiceStatus {
621 pub connections: usize,
623 pub inflight: usize,
625 pub handoff_requested: bool,
627 pub released: bool,
629 pub process: Option<rightkit_child_process::ProcessUsage>,
632}
633
634pub struct Hosted {
636 shared: Arc<Shared>,
637}
638
639impl Hosted {
640 pub fn record(&self) -> &ServiceRecord {
641 &self.shared.record
642 }
643 pub fn handoff_requested(&self) -> bool {
644 self.shared.handoff.load(Ordering::SeqCst)
645 }
646 pub fn inflight(&self) -> usize {
647 self.shared.inflight.load(Ordering::SeqCst)
648 }
649 pub fn status(&self) -> ServiceStatus {
652 ServiceStatus {
653 connections: self
654 .shared
655 .conns
656 .lock()
657 .unwrap_or_else(|e| e.into_inner())
658 .len(),
659 inflight: self.shared.inflight.load(Ordering::SeqCst),
660 handoff_requested: self.shared.handoff.load(Ordering::SeqCst),
661 released: *self
662 .shared
663 .released
664 .lock()
665 .unwrap_or_else(|e| e.into_inner()),
666 process: rightkit_child_process::process_usage().ok(),
667 }
668 }
669 pub fn metrics(&self) -> crate::metrics::HostMetrics {
674 self.shared.metrics.snapshot(
675 self.shared.inflight.load(Ordering::SeqCst),
676 self.shared.live_conns.load(Ordering::SeqCst),
677 )
678 }
679 pub fn wait_released(&self, timeout: Duration) -> bool {
681 let g = self
682 .shared
683 .released
684 .lock()
685 .unwrap_or_else(|e| e.into_inner());
686 let (g, _) = self
687 .shared
688 .released_cv
689 .wait_timeout_while(g, timeout, |r| !*r)
690 .unwrap_or_else(|e| e.into_inner());
691 *g
692 }
693 pub fn shutdown(&self) {
694 self.shared.release();
695 }
696}
697
698impl Drop for Hosted {
699 fn drop(&mut self) {
700 self.shared.release();
701 }
702}
703
704#[allow(clippy::result_large_err)]
710pub fn host(
711 suite: &Suite,
712 cfg: ServiceConfig,
713 handler: Arc<dyn Handler>,
714) -> Result<Hosted, HostError> {
715 host_with_options(suite, cfg, handler, HostOptions::default())
716}
717
718#[allow(clippy::result_large_err)]
720pub fn host_with_options(
721 suite: &Suite,
722 cfg: ServiceConfig,
723 handler: Arc<dyn Handler>,
724 options: HostOptions,
725) -> Result<Hosted, HostError> {
726 start_host(suite, cfg, handler, options, WatchTuning::PRODUCTION)
727}
728
729#[allow(clippy::result_large_err)]
730fn start_host(
731 suite: &Suite,
732 cfg: ServiceConfig,
733 handler: Arc<dyn Handler>,
734 options: HostOptions,
735 watch: WatchTuning,
736) -> Result<Hosted, HostError> {
737 if !valid_service_name(&cfg.service) {
738 return Err(HostError::InvalidName(cfg.service.clone()));
739 }
740 let dir = suite.service_dir(&cfg.service);
741 make_private_dir(&dir)?;
742 let me = std::process::id();
743 let deadline = Instant::now() + cfg.takeover_timeout;
744 let mut requested = false;
745 let lock_path = dir.join("election.lock");
746 let endpoint = match cfg.endpoint.clone() {
747 Some(e) => e,
748 None => try_endpoint_for(suite, &cfg.service).map_err(HostError::Endpoint)?,
749 };
750 let mut backoff = Duration::from_millis(5);
751 let (listener, election) = loop {
752 let election = try_lock_exclusive(&lock_path)?;
756 let current = try_read_record(suite, &cfg.service);
759 let unreadable = current
760 .as_ref()
761 .err()
762 .map(|e| std::io::Error::new(e.kind(), e.to_string()));
763 match current {
764 Ok(Some(rec)) if record_process_matches(&rec) && probe_endpoint(&rec.endpoint) => {
765 let their = resolve_rank(&cfg, &rec.role, rec.rank);
766 if their == RANK_UNKNOWN || cfg.rank >= their {
767 return Err(HostError::Held(rec));
768 }
769 if !requested {
770 let t = TakeoverRequest {
771 role: cfg.role.clone(),
772 rank: cfg.rank,
773 pid: me,
774 requested_at_ms: now_ms(),
775 };
776 let request_path = takeover_path(suite, &cfg.service);
777 write_atomic(&request_path, &serde_json::to_vec(&t).unwrap_or_default())?;
778 takeover_watch::poke(&request_path);
780 requested = true;
781 }
782 }
783 Ok(_) if election.is_some() => {
784 let _ = std::fs::remove_file(record_path(suite, &cfg.service));
787 match Listener::bind(&endpoint, options.max_pipe_instances) {
788 Ok(listener) => break (listener, election.unwrap()),
789 Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {}
792 Err(e) => return Err(HostError::Io(e)),
793 }
794 }
795 _ => {}
798 }
799 drop(election);
800 let remaining = deadline.saturating_duration_since(Instant::now());
801 if remaining.is_zero() {
802 clear_takeover_if_owner(suite, &cfg.service, me);
803 return Err(match unreadable {
806 Some(e) => HostError::Io(e),
807 None => HostError::TakeoverTimeout,
808 });
809 }
810 std::thread::sleep(backoff.min(remaining));
811 backoff = (backoff * 2).min(Duration::from_millis(50));
812 };
813 let record = ServiceRecord {
814 protocol_major: PROTOCOL_MAJOR,
815 protocol_minor: PROTOCOL_MINOR,
816 service: cfg.service.clone(),
817 role: cfg.role.clone(),
818 rank: cfg.rank,
819 pid: me,
820 endpoint: endpoint.clone(),
821 version: cfg.version.clone(),
822 engine_version: cfg.version.clone(),
823 started_ms: now_ms(),
824 process_started_ms: crate::discovery::own_start_ms().unwrap_or(0),
825 heartbeat_ms: 0,
826 state: String::new(),
827 };
828 if let Err(e) = write_atomic(
829 &record_path(suite, &cfg.service),
830 &serde_json::to_vec_pretty(&record).unwrap_or_default(),
831 ) {
832 listener.cleanup();
833 return Err(HostError::Io(e));
834 }
835 if read_takeover(suite, &cfg.service).is_some_and(|t| t.pid == me) {
836 let _ = std::fs::remove_file(takeover_path(suite, &cfg.service));
837 }
838 let shared = Arc::new(Shared {
839 suite: suite.clone(),
840 cfg,
841 handler,
842 listener,
843 election: Mutex::new(Some(election)),
844 record,
845 stop: AtomicBool::new(false),
846 handoff: Arc::new(AtomicBool::new(false)),
847 released: Mutex::new(false),
848 released_cv: Condvar::new(),
849 inflight: AtomicUsize::new(0),
850 next_conn: AtomicU64::new(1),
851 conns: Mutex::new(HashMap::new()),
852 bell: Arc::new(Bell::new()),
853 watch,
854 write_timeout: options.write_timeout,
855 max_message: options.max_message,
856 max_connections: options.max_connections,
857 live_conns: Arc::new(AtomicUsize::new(0)),
858 reserved_slots: options.reserved_slots,
859 supervisor: options.get_supervisor_pid(),
860 reserved_conns: Arc::new(AtomicUsize::new(0)),
861 pre_handshake: Arc::new(AtomicUsize::new(0)),
862 handshake_timeout: options.handshake_timeout,
863 fails: Mutex::new(crate::fail_limit::FailLimiter::new()),
864 metrics: crate::metrics::Metrics::new(if cfg!(unix) {
865 options.max_connections
866 } else {
867 options.max_pipe_instances as usize
868 }),
869 });
870 takeover_watch::register(
871 takeover_path(&shared.suite, &shared.cfg.service),
872 &shared.bell,
873 );
874 shared.event(ServiceEvent::Started {
875 service: shared.cfg.service.clone(),
876 endpoint,
877 rank: shared.cfg.rank,
878 });
879 let s = shared.clone();
880 if let Err(e) = std::thread::Builder::new()
881 .name("rk-svc-accept".into())
882 .spawn(move || accept_loop(s))
883 {
884 shared.release();
885 return Err(HostError::Io(e));
886 }
887 let s = shared.clone();
888 if let Err(e) = std::thread::Builder::new()
889 .name("rk-svc-takeover".into())
890 .spawn(move || takeover_monitor(s))
891 {
892 shared.release();
893 return Err(HostError::Io(e));
894 }
895 Ok(Hosted { shared })
896}
897
898pub(crate) fn now_ms() -> u64 {
899 std::time::SystemTime::now()
900 .duration_since(std::time::UNIX_EPOCH)
901 .map(|d| d.as_millis() as u64)
902 .unwrap_or(0)
903}
904
905fn reserved_slot(s: &Shared, peer: &crate::PeerIdentity) -> Option<ConnSlot> {
909 if !matches!((peer.pid, s.supervisor), (Some(pid), Some(owner)) if pid == owner) {
910 return None;
911 }
912 let slot = ConnSlot(s.reserved_conns.clone());
913 let used = s.reserved_conns.fetch_add(1, Ordering::SeqCst) + 1;
914 (used <= s.reserved_slots).then_some(slot)
915}
916
917fn accept_loop(s: Arc<Shared>) {
918 let mut full_reported = false;
921 while !s.stop.load(Ordering::SeqCst) {
922 match s.listener.accept() {
923 Ok(Accepted::Conn(conn)) => {
924 if s.stop.load(Ordering::SeqCst) {
925 break;
926 }
927 let table = ConnSlot(s.live_conns.clone());
930 let live = s.live_conns.fetch_add(1, Ordering::SeqCst) + 1;
931 s.metrics.note_connections(live);
932 let slot = if !cfg!(unix) || live <= s.max_connections {
933 full_reported = false;
935 table
936 } else {
937 drop(table);
938 match reserved_slot(&s, &conn.peer) {
939 Some(slot) => slot,
940 None => {
941 (conn.shutdown)();
942 s.metrics.note_refused();
943 s.event(ServiceEvent::PeerRejected {
944 reason: format!(
945 "connection limit of {} reached",
946 s.max_connections
947 ),
948 });
949 if !std::mem::replace(&mut full_reported, true) {
950 s.event(ServiceEvent::TableFull {
951 limit: s.max_connections,
952 reserved: s.reserved_slots,
953 pre_handshake: s.pre_handshake.load(Ordering::SeqCst),
954 attached: s
955 .conns
956 .lock()
957 .unwrap_or_else(|e| e.into_inner())
958 .len(),
959 peer_pid: conn.peer.pid,
960 });
961 }
962 continue;
963 }
964 }
965 };
966 let pre = PreHandshake::enter(&s.pre_handshake);
967 let s2 = s.clone();
968 let _ = std::thread::Builder::new()
969 .name("rk-svc-conn".into())
970 .spawn(move || {
971 let _slot = slot;
972 serve_conn(s2, conn, pre)
973 });
974 }
975 Ok(Accepted::Rejected(reason)) => {
978 if !s.stop.load(Ordering::SeqCst) {
979 s.event(ServiceEvent::PeerRejected { reason });
980 }
981 }
982 Ok(Accepted::Unidentified(refusal)) => {
983 if !s.stop.load(Ordering::SeqCst) {
984 s.event(ServiceEvent::PeerUnidentified {
985 pid: refusal.pid,
986 image: refusal.image,
987 kind: refusal.kind,
988 reason: refusal.reason,
989 });
990 }
991 }
992 Ok(Accepted::Vanished) => {}
993 Err(_) => {
994 if s.stop.load(Ordering::SeqCst) {
995 break;
996 }
997 std::thread::sleep(Duration::from_millis(20));
998 }
999 }
1000 }
1001}
1002
1003type FileStamp = (Option<SystemTime>, u64);
1005
1006fn file_stamp(path: &std::path::Path) -> Option<FileStamp> {
1007 let md = std::fs::metadata(path).ok()?;
1008 Some((md.modified().ok(), md.len()))
1009}
1010
1011fn obliging_rank(
1014 cfg: &ServiceConfig,
1015 own_pid: u32,
1016 alive: fn(u32) -> bool,
1017 t: &TakeoverRequest,
1018 now: u64,
1019) -> Option<u8> {
1020 let max_age = cfg.takeover_timeout.as_millis() as u64;
1021 let fresh = now.saturating_sub(t.requested_at_ms) <= max_age + 5_000;
1022 let their = resolve_rank(cfg, &t.role, t.rank);
1023 let obliges =
1024 their != RANK_UNKNOWN && their < cfg.rank && t.pid != own_pid && alive(t.pid) && fresh;
1025 obliges.then_some(their)
1026}
1027
1028fn takeover_monitor(s: Arc<Shared>) {
1038 let path = takeover_path(&s.suite, &s.cfg.service);
1039 let mut judged: Option<FileStamp> = None;
1040 let mut look_now = false;
1041 loop {
1042 if s.stop.load(Ordering::SeqCst) {
1043 return;
1044 }
1045 let force = std::mem::take(&mut look_now);
1046 match file_stamp(&path) {
1047 None => judged = None,
1048 Some(stamp) if force || judged != Some(stamp) => {
1049 judged = Some(stamp);
1050 match try_read_takeover(&s.suite, &s.cfg.service) {
1051 Ok(Some(t)) => {
1052 let now = now_ms();
1053 let obliging = obliging_rank(&s.cfg, s.record.pid, s.watch.alive, &t, now);
1054 if let Some(their) = obliging {
1055 hand_off(&s, t, their);
1056 return;
1057 }
1058 }
1059 Ok(None) => {}
1060 Err(_) => judged = None,
1064 }
1065 }
1066 Some(_) => {}
1067 }
1068 match s.bell.wait(s.watch.interval) {
1069 Wake::Stop => return,
1070 Wake::Poke => look_now = true,
1071 Wake::Timeout => {}
1072 }
1073 }
1074}
1075
1076fn hand_off(s: &Shared, t: TakeoverRequest, their: u8) {
1079 s.handoff.store(true, Ordering::SeqCst);
1080 s.event(ServiceEvent::HandoffRequested {
1081 by_role: t.role,
1082 by_rank: their,
1083 });
1084 s.handler.on_handoff();
1085 let until = Instant::now() + s.cfg.handoff_grace;
1086 while s.inflight.load(Ordering::SeqCst) > 0
1087 && Instant::now() < until
1088 && !s.stop.load(Ordering::SeqCst)
1089 {
1090 std::thread::sleep(Duration::from_millis(10));
1091 }
1092 s.release();
1093}
1094
1095fn frame_err(e: &FrameError) -> ServiceError {
1096 ServiceError::new("bad_frame", e.to_string())
1097}
1098
1099fn serve_conn(s: Arc<Shared>, conn: Conn, mut pre: PreHandshake) {
1100 let Conn {
1101 reader,
1102 writer,
1103 peer_closed,
1104 shutdown,
1105 peer,
1106 } = conn;
1107 let mut reader = BufReader::new(reader);
1108 let writer = Arc::new(Mutex::new(writer));
1109 let limit = s.cfg.frame_limit;
1110 let bound = s
1111 .write_timeout
1112 .map(|d| (d, DeadlineWatch::start(shutdown.clone())));
1113 let _watcher_ends = bound.as_ref().map(|(_, b)| FinishOnDrop(b.clone()));
1115 let write = {
1119 let w = writer.clone();
1120 let bound = bound.clone();
1121 move |v: &Value| -> bool {
1122 let mut w = w.lock().unwrap_or_else(|e| e.into_inner());
1123 if let Some((d, b)) = &bound {
1124 b.set(Some(Instant::now() + *d));
1125 }
1126 let r = match write_json_line(&mut *w, v, limit) {
1127 Ok(()) => {
1128 let _ = w.flush();
1129 true
1130 }
1131 Err(FrameError::TooLarge { .. }) => false,
1132 Err(_) => true,
1133 };
1134 if let Some((_, b)) = &bound {
1135 b.set(None);
1136 }
1137 r
1138 }
1139 };
1140 let send = {
1141 let write = write.clone();
1142 move |v: &Value| {
1143 write(v);
1144 }
1145 };
1146 let mut buf = Vec::new();
1147 let conn_key = s.next_conn.fetch_add(1, Ordering::SeqCst);
1149 let _forget_fails = ForgetFails {
1150 s: s.clone(),
1151 conn: conn_key,
1152 };
1153 let handshake = s.handshake_timeout.map(|d| {
1157 let (s, shutdown) = (s.clone(), shutdown.clone());
1158 let watch = DeadlineWatch::start(Arc::new(move || {
1159 s.event(ServiceEvent::ConnectionRejected {
1160 app: String::new(),
1161 reason: format!("handshake deadline {} ms passed", d.as_millis()),
1162 });
1163 shutdown();
1164 }));
1165 watch.set(Some(Instant::now() + d));
1166 watch
1167 });
1168 let _handshake_ends = handshake.as_ref().map(|w| FinishOnDrop(w.clone()));
1169 let hello: Value = match read_json_line(&mut reader, &mut buf, 64 * 1024) {
1171 Ok(Some(v)) => v,
1172 _ => return, };
1174 let app = hello["app"].as_str().unwrap_or("").to_string();
1175 let reject = |reason: &str, code: &str, retryable: bool| {
1176 let e = ServiceError {
1177 code: code.into(),
1178 message: reason.into(),
1179 retryable,
1180 details: None,
1181 };
1182 send(&json!({"type": "hello", "ok": false, "error": e.to_value()}));
1183 s.event(ServiceEvent::ConnectionRejected {
1184 app: app.clone(),
1185 reason: reason.into(),
1186 });
1187 shutdown();
1188 };
1189 if hello["type"] != "hello" || app.is_empty() {
1190 return reject(
1191 "first frame must be hello with an app label",
1192 "bad_hello",
1193 false,
1194 );
1195 }
1196 if let Some(secret) = &s.cfg.private_secret {
1197 if !secret.verify(hello["secret"].as_str().unwrap_or("")) {
1200 let now = Instant::now();
1201 let limited = s
1202 .fails
1203 .lock()
1204 .unwrap_or_else(|e| e.into_inner())
1205 .refuses_unverified(conn_key, now);
1206 if limited {
1207 return reject(
1208 "too many failed authentications; wait before reconnecting",
1209 crate::error::ERROR_RATE_LIMITED,
1210 false,
1211 );
1212 }
1213 s.fails
1214 .lock()
1215 .unwrap_or_else(|e| e.into_inner())
1216 .record_failure(conn_key, now);
1217 return reject(
1218 "missing or wrong secret",
1219 crate::error::ERROR_UNAUTHENTICATED,
1220 false,
1221 );
1222 }
1223 }
1224 if hello["protocol_major"].as_u64() != Some(u64::from(PROTOCOL_MAJOR)) {
1225 return reject("protocol major mismatch", "protocol_mismatch", false);
1226 }
1227 if s.handoff.load(Ordering::SeqCst) {
1228 return reject(
1229 "host is handing off",
1230 crate::error::ERROR_HOST_HANDOFF,
1231 true,
1232 );
1233 }
1234 if !s.cfg.allowlist.app_known(&app) {
1235 return reject(
1236 "app is not on the service allowlist",
1237 crate::error::ERROR_NOT_ALLOWED,
1238 false,
1239 );
1240 }
1241 let id = conn_key;
1242 s.conns
1243 .lock()
1244 .unwrap_or_else(|e| e.into_inner())
1245 .insert(id, shutdown.clone());
1246 pre.settle();
1249 if let Some(watch) = &handshake {
1250 watch.finish();
1251 }
1252 s.event(ServiceEvent::ConnectionOpened {
1253 conn: id,
1254 app: app.clone(),
1255 });
1256 send(&json!({
1257 "type": "hello", "ok": true, "service": s.cfg.service, "role": s.cfg.role, "rank": s.cfg.rank,
1258 "version": s.cfg.version, "protocol_major": PROTOCOL_MAJOR, "protocol_minor": PROTOCOL_MINOR,
1259 "methods": s.cfg.allowlist.methods_for(&app),
1260 "frame_limit": limit, "max_message": s.max_message,
1261 "chunk_bytes": crate::chunk::chunk_bytes_for(limit),
1262 }));
1263
1264 let caller = Caller::local_app(app.clone(), peer.clone());
1265 let closed_notified = Arc::new(AtomicBool::new(false));
1266 let notify_closed = {
1267 let (s, flag) = (s.clone(), closed_notified.clone());
1268 move || {
1269 if !flag.swap(true, Ordering::SeqCst) {
1270 s.handler.connection_closed(id);
1271 }
1272 }
1273 };
1274 let chunk_bytes = crate::chunk::chunk_bytes_for(limit);
1277 let mut uploads = crate::chunk::Uploads::default();
1278 let mut download: Option<crate::chunk::Download> = None;
1279 let mut downloads_made: u64 = 0;
1280 loop {
1282 let req: Value = match read_json_line(&mut reader, &mut buf, limit) {
1283 Ok(Some(v)) => v,
1284 Ok(None) | Err(FrameError::Io(_)) => break,
1285 Err(e) => {
1286 send(
1287 &json!({"type": "response", "id": 0, "ok": false, "error": frame_err(&e).to_value()}),
1288 );
1289 continue;
1290 }
1291 };
1292 if let Some(secret) = &s.cfg.private_secret {
1293 if !secret.verify(req["secret"].as_str().unwrap_or("")) {
1296 let now = Instant::now();
1297 let limited = s
1298 .fails
1299 .lock()
1300 .unwrap_or_else(|e| e.into_inner())
1301 .refuses_unverified(conn_key, now);
1302 if limited {
1303 s.event(ServiceEvent::ConnectionRejected {
1304 app: app.clone(),
1305 reason: "too many failed authentications".into(),
1306 });
1307 send(
1308 &json!({"type": "response", "id": req["id"].as_u64().unwrap_or(0), "ok": false,
1309 "error": ServiceError::new(crate::error::ERROR_RATE_LIMITED, "too many failed authentications; wait before reconnecting").to_value()}),
1310 );
1311 break;
1312 }
1313 s.fails
1314 .lock()
1315 .unwrap_or_else(|e| e.into_inner())
1316 .record_failure(conn_key, now);
1317 s.event(ServiceEvent::ConnectionRejected {
1318 app: app.clone(),
1319 reason: "missing or wrong secret".into(),
1320 });
1321 send(
1322 &json!({"type": "response", "id": req["id"].as_u64().unwrap_or(0), "ok": false,
1323 "error": ServiceError::new(crate::error::ERROR_UNAUTHENTICATED, "missing or wrong secret").to_value()}),
1324 );
1325 break;
1326 }
1327 }
1328 let Some(rid) = req["id"].as_u64().filter(|_| req["type"] == "request") else {
1329 send(
1330 &json!({"type": "response", "id": 0, "ok": false, "error": ServiceError::new("bad_request", "expected {type:request,id,method}").to_value()}),
1331 );
1332 continue;
1333 };
1334 let respond_err = |e: ServiceError| {
1335 send(&json!({"type": "response", "id": rid, "ok": false, "error": e.to_value()}))
1336 };
1337 uploads.expire();
1339 let mut method = req["method"].as_str().unwrap_or("").to_string();
1340 let chunked_ok = req["chunked_ok"] == true && s.max_message > 0;
1341 if s.handoff.load(Ordering::SeqCst) {
1342 respond_err(ServiceError::handoff());
1343 continue;
1344 }
1345 let mut assembled: Option<Value> = None;
1348 if method == crate::chunk::METHOD_FETCH {
1349 let answer = match &download {
1350 Some(d) => d.fetch(req.get("params").unwrap_or(&Value::Null)),
1351 None => Err(ServiceError::new(
1352 crate::error::ERROR_BAD_CHUNK,
1353 "no chunked result is held on this connection",
1354 )),
1355 };
1356 match answer {
1357 Ok(v) => send(&json!({"type": "response", "id": rid, "ok": true, "result": v})),
1358 Err(e) => respond_err(e),
1359 }
1360 continue;
1361 }
1362 if method == crate::chunk::METHOD_UPLOAD {
1363 if s.max_message == 0 {
1364 respond_err(ServiceError::new(
1365 crate::error::ERROR_REQUEST_TOO_LARGE,
1366 "this host does not accept chunked requests",
1367 ));
1368 continue;
1369 }
1370 let authorize = |m: &str| {
1373 crate::contract::authorize_allowlist(&s.cfg.allowlist, &caller, m).inspect_err(
1374 |_| {
1375 s.event(ServiceEvent::Denied {
1376 conn: id,
1377 app: app.clone(),
1378 method: m.to_string(),
1379 });
1380 },
1381 )
1382 };
1383 match uploads.accept_with(
1384 req.get("params").unwrap_or(&Value::Null),
1385 s.max_message,
1386 &authorize,
1387 ) {
1388 Ok(crate::chunk::UploadStep::More { received }) => {
1389 send(&json!({"type": "response", "id": rid, "ok": true,
1390 "result": {"received": received}}));
1391 continue;
1392 }
1393 Ok(crate::chunk::UploadStep::Done { method: m, params }) => {
1394 method = m;
1395 assembled = Some(params);
1396 }
1397 Err(e) => {
1398 respond_err(e);
1399 continue;
1400 }
1401 }
1402 }
1403 if let Err(e) = crate::contract::authorize_allowlist(&s.cfg.allowlist, &caller, &method) {
1404 s.event(ServiceEvent::Denied {
1405 conn: id,
1406 app: app.clone(),
1407 method: method.clone(),
1408 });
1409 respond_err(e);
1410 continue;
1411 }
1412 if method == "ping" {
1413 send(
1414 &json!({"type": "response", "id": rid, "ok": true, "result": {"pong": true, "rank": s.cfg.rank}}),
1415 );
1416 continue;
1417 }
1418 let params =
1419 assembled.unwrap_or_else(|| req.get("params").cloned().unwrap_or_else(|| json!({})));
1420 let cx = new_context(
1421 id,
1422 caller.clone(),
1423 Arc::new(AtomicBool::new(false)),
1424 s.handoff.clone(),
1425 );
1426 let emit: Emit = {
1427 let (write, s, method) = (write.clone(), s.clone(), method.clone());
1428 Arc::new(move |ev| {
1429 if !write(&json!({"type": "event", "id": rid, "event": ev})) {
1430 s.event(ServiceEvent::FrameTooLarge {
1431 conn: id,
1432 method: method.clone(),
1433 kind: "event",
1434 limit,
1435 });
1436 }
1437 })
1438 };
1439 let depth = s.inflight.fetch_add(1, Ordering::SeqCst) + 1;
1440 s.metrics.note_inflight(depth);
1441 let started = Instant::now();
1442 let done = AtomicBool::new(false);
1443 let result = std::thread::scope(|sc| {
1444 let watcher = sc.spawn(|| {
1447 while !done.load(Ordering::SeqCst) {
1448 if peer_closed() {
1449 cx.closed.store(true, Ordering::SeqCst);
1450 notify_closed();
1451 return;
1452 }
1453 std::thread::park_timeout(Duration::from_millis(40));
1454 }
1455 });
1456 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1457 s.handler.call(&cx, &method, ¶ms, &emit)
1458 }))
1459 .unwrap_or_else(|_| Err(ServiceError::new("panic", "handler panicked")));
1460 done.store(true, Ordering::SeqCst);
1461 watcher.thread().unpark();
1462 r
1463 });
1464 s.inflight.fetch_sub(1, Ordering::SeqCst);
1465 s.metrics.record(
1466 &method,
1467 started.elapsed(),
1468 result.as_ref().err().map(|e| e.code.as_str()),
1469 );
1470 if cx.peer_closed() {
1471 break;
1472 }
1473 match result {
1474 Ok(v) => {
1475 if !write(&json!({"type": "response", "id": rid, "ok": true, "result": v})) {
1478 s.event(ServiceEvent::FrameTooLarge {
1479 conn: id,
1480 method: method.clone(),
1481 kind: "response",
1482 limit,
1483 });
1484 let held = chunked_ok
1487 .then(|| serde_json::to_string(&v).ok())
1488 .flatten()
1489 .filter(|text| text.len() <= s.max_message);
1490 drop(v);
1491 match held {
1492 Some(text) => {
1493 downloads_made += 1;
1494 let held = crate::chunk::Download::new(
1495 format!("d{downloads_made}"),
1496 text,
1497 chunk_bytes,
1498 );
1499 let descriptor = held.descriptor();
1500 download = Some(held);
1501 send(&json!({"type": "response", "id": rid, "ok": true,
1502 "result": Value::Null, "chunked": descriptor}));
1503 }
1504 None => respond_err(ServiceError::new(
1505 ERROR_RESPONSE_TOO_LARGE,
1506 format!(
1507 "the result of {method} is larger than the {limit}-byte frame limit"
1508 ),
1509 )),
1510 }
1511 }
1512 }
1513 Err(e) => respond_err(e),
1514 }
1515 }
1516 notify_closed();
1517 s.conns
1518 .lock()
1519 .unwrap_or_else(|e| e.into_inner())
1520 .remove(&id);
1521 shutdown();
1522}
1523
1524#[allow(dead_code)]
1525fn _assert_transport_used() {
1526 let _ = transport::current_uid;
1527}