kvbm_engine/leader/session/
endpoint.rs1use std::sync::Arc;
42use tokio::sync::{mpsc, watch};
43
44use anyhow::Result;
45
46use crate::InstanceId;
47
48use super::{
49 SessionId,
50 messages::{BlockInfo, SessionMessage, SessionStateSnapshot},
51 state::{AttachmentState, ControlRole, SessionPhase},
52 transport::MessageTransport,
53};
54
55pub struct SessionEndpoint {
64 session_id: SessionId,
65 instance_id: InstanceId,
66
67 control_role: ControlRole,
69 attachment: AttachmentState,
70 phase: SessionPhase,
71
72 transport: Arc<MessageTransport>,
74 msg_rx: mpsc::Receiver<SessionMessage>,
75 state_tx: watch::Sender<SessionStateSnapshot>,
76}
77
78impl SessionEndpoint {
79 pub fn new(
81 session_id: SessionId,
82 instance_id: InstanceId,
83 transport: Arc<MessageTransport>,
84 msg_rx: mpsc::Receiver<SessionMessage>,
85 ) -> Self {
86 let initial_state = SessionStateSnapshot {
87 phase: SessionPhase::default(),
88 control_role: ControlRole::default(),
89 g2_blocks: Vec::new(),
90 g3_pending: 0,
91 ready_layer_range: None,
92 };
93 let (state_tx, _) = watch::channel(initial_state);
94
95 Self {
96 session_id,
97 instance_id,
98 control_role: ControlRole::default(),
99 attachment: AttachmentState::default(),
100 phase: SessionPhase::default(),
101 transport,
102 msg_rx,
103 state_tx,
104 }
105 }
106
107 pub fn new_attached(
112 session_id: SessionId,
113 instance_id: InstanceId,
114 peer: InstanceId,
115 role: ControlRole,
116 phase: SessionPhase,
117 transport: Arc<MessageTransport>,
118 msg_rx: mpsc::Receiver<SessionMessage>,
119 ) -> Self {
120 let initial_state = SessionStateSnapshot {
121 phase,
122 control_role: role,
123 g2_blocks: Vec::new(),
124 g3_pending: 0,
125 ready_layer_range: None,
126 };
127 let (state_tx, _) = watch::channel(initial_state);
128
129 Self {
130 session_id,
131 instance_id,
132 control_role: role,
133 attachment: AttachmentState::Attached { peer },
134 phase,
135 transport,
136 msg_rx,
137 state_tx,
138 }
139 }
140
141 pub fn session_id(&self) -> SessionId {
147 self.session_id
148 }
149
150 pub fn instance_id(&self) -> InstanceId {
152 self.instance_id
153 }
154
155 pub fn control_role(&self) -> ControlRole {
157 self.control_role
158 }
159
160 pub fn is_attached(&self) -> bool {
162 self.attachment.is_attached()
163 }
164
165 pub fn peer(&self) -> Option<InstanceId> {
167 self.attachment.peer()
168 }
169
170 pub fn phase(&self) -> SessionPhase {
172 self.phase
173 }
174
175 pub fn is_complete(&self) -> bool {
177 self.phase.is_terminal()
178 }
179
180 pub fn set_phase(&mut self, phase: SessionPhase) {
186 self.phase = phase;
187 }
188
189 pub fn set_control_role(&mut self, role: ControlRole) {
194 self.control_role = role;
195 }
196
197 pub fn accept_attachment(&mut self, peer: InstanceId, role: ControlRole) {
201 self.attachment = AttachmentState::Attached { peer };
202 self.control_role = role;
203 }
204
205 pub fn detach(&mut self) -> Option<InstanceId> {
209 let peer = self.attachment.peer();
210 self.attachment = AttachmentState::Unattached;
211 self.control_role = ControlRole::Neutral;
212 peer
213 }
214
215 pub fn yield_control(&mut self) -> Result<()> {
220 if self.control_role != ControlRole::Controller {
221 anyhow::bail!("Cannot yield control: not currently Controller");
222 }
223 self.control_role = ControlRole::Neutral;
224 Ok(())
225 }
226
227 pub fn acquire_control(&mut self) -> Result<()> {
232 if self.control_role == ControlRole::Controller {
233 return Ok(());
235 }
236 self.control_role = ControlRole::Controller;
237 Ok(())
238 }
239
240 pub fn peer_yielded_control(&mut self) {
244 if self.control_role == ControlRole::Controllee {
247 self.control_role = ControlRole::Neutral;
248 }
249 }
250
251 pub fn peer_acquired_control(&mut self) -> Result<()> {
255 if self.control_role == ControlRole::Controller {
256 anyhow::bail!("Cannot transition to Controllee: currently Controller");
257 }
258 self.control_role = ControlRole::Controllee;
259 Ok(())
260 }
261
262 pub async fn recv(&mut self) -> Option<SessionMessage> {
270 self.msg_rx.recv().await
271 }
272
273 pub fn try_recv(&mut self) -> Result<SessionMessage, mpsc::error::TryRecvError> {
275 self.msg_rx.try_recv()
276 }
277
278 pub async fn send_attach(&self, peer: InstanceId, as_role: ControlRole) -> Result<()> {
284 let msg = SessionMessage::Attach {
285 peer: self.instance_id,
286 session_id: self.session_id,
287 as_role,
288 };
289 self.send_to(peer, msg).await
290 }
291
292 pub async fn send_detach(&self) -> Result<()> {
294 let peer = self
295 .peer()
296 .ok_or_else(|| anyhow::anyhow!("Cannot detach: not attached"))?;
297
298 let msg = SessionMessage::Detach {
299 peer: self.instance_id,
300 session_id: self.session_id,
301 };
302 self.send_to(peer, msg).await
303 }
304
305 pub async fn send_yield_control(&self) -> Result<()> {
307 let peer = self
308 .peer()
309 .ok_or_else(|| anyhow::anyhow!("Cannot yield: not attached"))?;
310
311 let msg = SessionMessage::YieldControl {
312 peer: self.instance_id,
313 session_id: self.session_id,
314 };
315 self.send_to(peer, msg).await
316 }
317
318 pub async fn send_acquire_control(&self) -> Result<()> {
320 let peer = self
321 .peer()
322 .ok_or_else(|| anyhow::anyhow!("Cannot acquire: not attached"))?;
323
324 let msg = SessionMessage::AcquireControl {
325 peer: self.instance_id,
326 session_id: self.session_id,
327 };
328 self.send_to(peer, msg).await
329 }
330
331 pub async fn send_to(&self, peer: InstanceId, msg: SessionMessage) -> Result<()> {
333 self.transport.send_session(peer, msg).await
334 }
335
336 pub async fn send(&self, msg: SessionMessage) -> Result<()> {
338 let peer = self
339 .peer()
340 .ok_or_else(|| anyhow::anyhow!("Cannot send: not attached"))?;
341 self.send_to(peer, msg).await
342 }
343
344 pub fn publish_state(&self, g2_blocks: Vec<BlockInfo>, g3_pending: usize) {
352 let _ = self.state_tx.send(SessionStateSnapshot {
353 phase: self.phase,
354 control_role: self.control_role,
355 g2_blocks,
356 g3_pending,
357 ready_layer_range: None,
358 });
359 }
360
361 pub fn publish_state_with_layer_range(
365 &self,
366 g2_blocks: Vec<BlockInfo>,
367 g3_pending: usize,
368 layer_range: Option<std::ops::Range<usize>>,
369 ) {
370 let _ = self.state_tx.send(SessionStateSnapshot {
371 phase: self.phase,
372 control_role: self.control_role,
373 g2_blocks,
374 g3_pending,
375 ready_layer_range: layer_range,
376 });
377 }
378
379 pub fn state_rx(&self) -> watch::Receiver<SessionStateSnapshot> {
384 self.state_tx.subscribe()
385 }
386
387 pub fn transport(&self) -> &Arc<MessageTransport> {
389 &self.transport
390 }
391}
392
393pub type SessionMessageTx = mpsc::Sender<SessionMessage>;
395
396pub fn session_message_channel(
398 buffer: usize,
399) -> (SessionMessageTx, mpsc::Receiver<SessionMessage>) {
400 mpsc::channel(buffer)
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use dashmap::DashMap;
407
408 fn create_test_transport() -> Arc<MessageTransport> {
409 Arc::new(MessageTransport::local(
410 Arc::new(DashMap::new()),
411 Arc::new(DashMap::new()),
412 ))
413 }
414
415 #[test]
416 fn test_endpoint_initial_state() {
417 let (_, rx) = mpsc::channel(32);
418 let transport = create_test_transport();
419 let session_id = SessionId::new_v4();
420 let instance_id = InstanceId::new_v4();
421
422 let endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
423
424 assert_eq!(endpoint.session_id(), session_id);
425 assert_eq!(endpoint.instance_id(), instance_id);
426 assert_eq!(endpoint.control_role(), ControlRole::Neutral);
427 assert!(!endpoint.is_attached());
428 assert!(endpoint.peer().is_none());
429 assert_eq!(endpoint.phase(), SessionPhase::Searching);
430 assert!(!endpoint.is_complete());
431 }
432
433 #[test]
434 fn test_endpoint_attachment() {
435 let (_, rx) = mpsc::channel(32);
436 let transport = create_test_transport();
437 let session_id = SessionId::new_v4();
438 let instance_id = InstanceId::new_v4();
439 let peer_id = InstanceId::new_v4();
440
441 let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
442
443 endpoint.accept_attachment(peer_id, ControlRole::Controllee);
445
446 assert!(endpoint.is_attached());
447 assert_eq!(endpoint.peer(), Some(peer_id));
448 assert_eq!(endpoint.control_role(), ControlRole::Controllee);
449
450 let detached = endpoint.detach();
452 assert_eq!(detached, Some(peer_id));
453 assert!(!endpoint.is_attached());
454 assert_eq!(endpoint.control_role(), ControlRole::Neutral);
455 }
456
457 #[test]
458 fn test_endpoint_pre_attached() {
459 let (_, rx) = mpsc::channel(32);
460 let transport = create_test_transport();
461 let session_id = SessionId::new_v4();
462 let instance_id = InstanceId::new_v4();
463 let peer_id = InstanceId::new_v4();
464
465 let endpoint = SessionEndpoint::new_attached(
466 session_id,
467 instance_id,
468 peer_id,
469 ControlRole::Controller,
470 SessionPhase::Holding,
471 transport,
472 rx,
473 );
474
475 assert!(endpoint.is_attached());
476 assert_eq!(endpoint.peer(), Some(peer_id));
477 assert_eq!(endpoint.control_role(), ControlRole::Controller);
478 assert_eq!(endpoint.phase(), SessionPhase::Holding);
479 }
480
481 #[test]
482 fn test_control_transitions() {
483 let (_, rx) = mpsc::channel(32);
484 let transport = create_test_transport();
485 let session_id = SessionId::new_v4();
486 let instance_id = InstanceId::new_v4();
487 let peer_id = InstanceId::new_v4();
488
489 let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
490 endpoint.accept_attachment(peer_id, ControlRole::Controller);
491
492 assert!(endpoint.yield_control().is_ok());
494 assert_eq!(endpoint.control_role(), ControlRole::Neutral);
495
496 assert!(endpoint.yield_control().is_err());
498
499 assert!(endpoint.acquire_control().is_ok());
501 assert_eq!(endpoint.control_role(), ControlRole::Controller);
502 }
503
504 #[test]
505 fn test_phase_transitions() {
506 let (_, rx) = mpsc::channel(32);
507 let transport = create_test_transport();
508 let session_id = SessionId::new_v4();
509 let instance_id = InstanceId::new_v4();
510
511 let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
512
513 assert_eq!(endpoint.phase(), SessionPhase::Searching);
514 assert!(!endpoint.is_complete());
515
516 endpoint.set_phase(SessionPhase::Holding);
517 assert_eq!(endpoint.phase(), SessionPhase::Holding);
518
519 endpoint.set_phase(SessionPhase::Complete);
520 assert!(endpoint.is_complete());
521 }
522
523 #[tokio::test]
524 async fn test_state_publication() {
525 let (_, rx) = mpsc::channel(32);
526 let transport = create_test_transport();
527 let session_id = SessionId::new_v4();
528 let instance_id = InstanceId::new_v4();
529
530 let endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
531 let mut state_rx = endpoint.state_rx();
532
533 let state = state_rx.borrow().clone();
535 assert_eq!(state.phase, SessionPhase::Searching);
536 assert_eq!(state.g2_blocks.len(), 0);
537
538 endpoint.publish_state(vec![], 5);
540
541 state_rx.changed().await.unwrap();
543 let state = state_rx.borrow().clone();
544 assert_eq!(state.g3_pending, 5);
545 }
546}