Implement section runner state publishing
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									742a00892d
								
							
						
					
					
						commit
						7541618ec8
					
				| @ -9,6 +9,7 @@ mod program_runner; | ||||
| mod schedule; | ||||
| mod section_interface; | ||||
| mod section_runner; | ||||
| mod section_runner_json; | ||||
| #[cfg(test)] | ||||
| mod trace_listeners; | ||||
| mod update_listener; | ||||
| @ -67,7 +68,13 @@ async fn main() -> Result<()> { | ||||
|     let update_listener = { | ||||
|         let section_events = section_runner.subscribe().await?; | ||||
|         let program_events = program_runner.subscribe().await?; | ||||
|         UpdateListener::start(section_events, program_events, mqtt_interface.clone()) | ||||
|         let sec_runner_state = section_runner.state_receiver(); | ||||
|         UpdateListener::start( | ||||
|             section_events, | ||||
|             program_events, | ||||
|             sec_runner_state, | ||||
|             mqtt_interface.clone(), | ||||
|         ) | ||||
|     }; | ||||
| 
 | ||||
|     program_runner.update_sections(sections.clone()).await?; | ||||
|  | ||||
| @ -1,4 +1,8 @@ | ||||
| use crate::model::{Program, ProgramId, Programs, Section, SectionId, Sections}; | ||||
| use crate::{ | ||||
|     model::{Program, ProgramId, Programs, Section, SectionId, Sections}, | ||||
|     section_runner::SecRunnerState, | ||||
|     section_runner_json::SecRunnerStateJson, | ||||
| }; | ||||
| use eyre::WrapErr; | ||||
| use rumqttc::{LastWill, MqttOptions, QoS}; | ||||
| use std::{ | ||||
| @ -52,6 +56,10 @@ where | ||||
|     fn program_running(&self, program_id: ProgramId) -> String { | ||||
|         format!("{}/programs/{}/running", self.prefix.as_ref(), program_id) | ||||
|     } | ||||
| 
 | ||||
|     fn section_runner(&self) -> String { | ||||
|         format!("{}/section_runner", self.prefix.as_ref()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| @ -165,6 +173,7 @@ impl MqttInterface { | ||||
|             .wrap_err("failed to publish section") | ||||
|     } | ||||
| 
 | ||||
|     // Section state can be derived from section runner state...
 | ||||
|     pub async fn publish_section_state( | ||||
|         &mut self, | ||||
|         section_id: SectionId, | ||||
| @ -201,6 +210,13 @@ impl MqttInterface { | ||||
|             .await | ||||
|             .wrap_err("failed to publish program running") | ||||
|     } | ||||
| 
 | ||||
|     pub async fn publish_section_runner(&mut self, sr_state: &SecRunnerState) -> eyre::Result<()> { | ||||
|         let json: SecRunnerStateJson = sr_state.into(); | ||||
|         self.publish_data(self.topics.section_runner(), &json) | ||||
|             .await | ||||
|             .wrap_err("failed to publish section runner") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct MqttInterfaceTask { | ||||
|  | ||||
| @ -12,7 +12,7 @@ use std::{ | ||||
| use thiserror::Error; | ||||
| use tokio::{ | ||||
|     spawn, | ||||
|     sync::{broadcast, mpsc, oneshot}, | ||||
|     sync::{broadcast, mpsc, oneshot, watch}, | ||||
|     time::{delay_for, Instant}, | ||||
| }; | ||||
| use tracing::{debug, trace, trace_span, warn}; | ||||
| @ -21,6 +21,12 @@ use tracing_futures::Instrument; | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||||
| pub struct SectionRunHandle(i32); | ||||
| 
 | ||||
| impl SectionRunHandle { | ||||
|     pub fn into_inner(self) -> i32 { | ||||
|         self.0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct SectionRunnerInner { | ||||
|     next_run_id: AtomicI32, | ||||
| @ -77,11 +83,11 @@ pub enum SecRunState { | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct SecRun { | ||||
|     handle: SectionRunHandle, | ||||
|     section: SectionRef, | ||||
|     duration: Duration, | ||||
|     total_duration: Duration, | ||||
|     state: SecRunState, | ||||
|     pub(crate) handle: SectionRunHandle, | ||||
|     pub(crate) section: SectionRef, | ||||
|     pub(crate) duration: Duration, | ||||
|     pub(crate) total_duration: Duration, | ||||
|     pub(crate) state: SecRunState, | ||||
| } | ||||
| 
 | ||||
| impl SecRun { | ||||
| @ -109,8 +115,8 @@ pub type SecRunQueue = im::Vector<Arc<SecRun>>; | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct SecRunnerState { | ||||
|     run_queue: SecRunQueue, | ||||
|     paused: bool, | ||||
|     pub(crate) run_queue: SecRunQueue, | ||||
|     pub(crate) paused: bool, | ||||
| } | ||||
| 
 | ||||
| impl Default for SecRunnerState { | ||||
| @ -122,19 +128,24 @@ impl Default for SecRunnerState { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub type SecRunnerStateRecv = watch::Receiver<SecRunnerState>; | ||||
| 
 | ||||
| struct RunnerTask { | ||||
|     interface: Arc<dyn SectionInterface + Sync>, | ||||
|     msg_recv: mpsc::Receiver<RunnerMsg>, | ||||
|     running: bool, | ||||
|     delay_future: OptionFuture<tokio::time::Delay>, | ||||
|     event_send: Option<SectionEventSend>, | ||||
|     state_send: watch::Sender<SecRunnerState>, | ||||
|     quit_tx: Option<oneshot::Sender<()>>, | ||||
|     did_change: bool, | ||||
| } | ||||
| 
 | ||||
| impl RunnerTask { | ||||
|     fn new( | ||||
|         interface: Arc<dyn SectionInterface + Sync>, | ||||
|         msg_recv: mpsc::Receiver<RunnerMsg>, | ||||
|         state_send: watch::Sender<SecRunnerState>, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             interface, | ||||
| @ -142,7 +153,9 @@ impl RunnerTask { | ||||
|             running: true, | ||||
|             delay_future: None.into(), | ||||
|             event_send: None, | ||||
|             state_send, | ||||
|             quit_tx: None, | ||||
|             did_change: false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -181,6 +194,7 @@ impl RunnerTask { | ||||
|             run.handle.clone(), | ||||
|             run.section.clone(), | ||||
|         )); | ||||
|         self.did_change = true; | ||||
|     } | ||||
| 
 | ||||
|     fn finish_run(&mut self, run: &mut Arc<SecRun>) { | ||||
| @ -194,6 +208,7 @@ impl RunnerTask { | ||||
|                 run.handle.clone(), | ||||
|                 run.section.clone(), | ||||
|             )); | ||||
|             self.did_change = true; | ||||
|         } else { | ||||
|             warn!( | ||||
|                 section_id = run.section.id, | ||||
| @ -215,6 +230,7 @@ impl RunnerTask { | ||||
|             run.handle.clone(), | ||||
|             run.section.clone(), | ||||
|         )); | ||||
|         self.did_change = true; | ||||
|     } | ||||
| 
 | ||||
|     fn pause_run(&mut self, run: &mut Arc<SecRun>) { | ||||
| @ -246,6 +262,7 @@ impl RunnerTask { | ||||
|             run.handle.clone(), | ||||
|             run.section.clone(), | ||||
|         )); | ||||
|         self.did_change = true; | ||||
|     } | ||||
| 
 | ||||
|     fn unpause_run(&mut self, run: &mut Arc<SecRun>) { | ||||
| @ -277,6 +294,7 @@ impl RunnerTask { | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|         self.did_change = true; | ||||
|     } | ||||
| 
 | ||||
|     fn process_queue(&mut self, state: &mut SecRunnerState) { | ||||
| @ -335,6 +353,7 @@ impl RunnerTask { | ||||
|                 state | ||||
|                     .run_queue | ||||
|                     .push_back(Arc::new(SecRun::new(handle, section, duration))); | ||||
|                 self.did_change = true; | ||||
|             } | ||||
|             CancelRun(handle) => { | ||||
|                 for run in state.run_queue.iter_mut() { | ||||
| @ -356,10 +375,12 @@ impl RunnerTask { | ||||
|             Pause => { | ||||
|                 state.paused = true; | ||||
|                 self.send_event(SectionEvent::RunnerPause); | ||||
|                 self.did_change = true; | ||||
|             } | ||||
|             Unpause => { | ||||
|                 state.paused = false; | ||||
|                 self.send_event(SectionEvent::RunnerUnpause); | ||||
|                 self.did_change = true; | ||||
|             } | ||||
|             Subscribe(res_send) => { | ||||
|                 let event_recv = self.subscribe_event(); | ||||
| @ -373,12 +394,28 @@ impl RunnerTask { | ||||
|         let mut state = SecRunnerState::default(); | ||||
| 
 | ||||
|         while self.running { | ||||
|             // Process all pending messages
 | ||||
|             // This is so if there are many pending messages, the state
 | ||||
|             // is only broadcast once
 | ||||
|             while let Ok(msg) = self.msg_recv.try_recv() { | ||||
|                 self.handle_msg(Some(msg), &mut state); | ||||
|             } | ||||
| 
 | ||||
|             self.process_queue(&mut state); | ||||
| 
 | ||||
|             // If a change was made to state, broadcast it
 | ||||
|             if self.did_change { | ||||
|                 let _ = self.state_send.broadcast(state.clone()); | ||||
|                 self.did_change = false; | ||||
|             } | ||||
| 
 | ||||
|             let delay_done = || { | ||||
|                 trace!("delay done"); | ||||
|             }; | ||||
|             tokio::select! { | ||||
|                 msg = self.msg_recv.recv() => self.handle_msg(msg, &mut state), | ||||
|                 msg = self.msg_recv.recv() => { | ||||
|                     self.handle_msg(msg, &mut state) | ||||
|                 }, | ||||
|                 _ = &mut self.delay_future, if self.delay_future.is_some() => delay_done() | ||||
|             }; | ||||
|         } | ||||
| @ -417,16 +454,19 @@ impl From<oneshot::error::RecvError> for ChannelClosed { | ||||
| pub struct SectionRunner { | ||||
|     inner: Arc<SectionRunnerInner>, | ||||
|     msg_send: mpsc::Sender<RunnerMsg>, | ||||
|     state_recv: SecRunnerStateRecv, | ||||
| } | ||||
| 
 | ||||
| #[allow(dead_code)] | ||||
| impl SectionRunner { | ||||
|     pub fn new(interface: Arc<dyn SectionInterface + Sync>) -> Self { | ||||
|         let (msg_send, msg_recv) = mpsc::channel(8); | ||||
|         spawn(RunnerTask::new(interface, msg_recv).run()); | ||||
|         let (msg_send, msg_recv) = mpsc::channel(32); | ||||
|         let (state_send, state_recv) = watch::channel(SecRunnerState::default()); | ||||
|         spawn(RunnerTask::new(interface, msg_recv, state_send).run()); | ||||
|         Self { | ||||
|             inner: Arc::new(SectionRunnerInner::new()), | ||||
|             msg_send, | ||||
|             state_recv, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -476,6 +516,10 @@ impl SectionRunner { | ||||
|         let event_recv = res_recv.await?; | ||||
|         Ok(event_recv) | ||||
|     } | ||||
| 
 | ||||
|     pub fn state_receiver(&self) -> SecRunnerStateRecv { | ||||
|         self.state_recv.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | ||||
							
								
								
									
										75
									
								
								src/section_runner_json.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/section_runner_json.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| use crate::{ | ||||
|     model::SectionId, | ||||
|     section_runner::{SecRun, SecRunState, SecRunnerState}, | ||||
| }; | ||||
| use chrono::{DateTime, Utc}; | ||||
| use serde::Serialize; | ||||
| use std::time::SystemTime; | ||||
| use tokio::time::Instant; | ||||
| 
 | ||||
| #[derive(Clone, Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SecRunJson { | ||||
|     id: i32, | ||||
|     section: SectionId, | ||||
|     total_duration: f64, | ||||
|     duration: f64, | ||||
|     start_time: Option<String>, | ||||
|     pause_time: Option<String>, | ||||
|     unpause_time: Option<String>, | ||||
| } | ||||
| 
 | ||||
| impl SecRunJson { | ||||
|     fn from_run(run: &SecRun) -> Option<Self> { | ||||
|         let (now, system_now) = (Instant::now(), SystemTime::now()); | ||||
|         let instant_to_string = |instant: Instant| -> String { | ||||
|             DateTime::<Utc>::from(system_now - now.duration_since(instant)).to_rfc3339() | ||||
|         }; | ||||
|         let (start_time, pause_time) = match run.state { | ||||
|             SecRunState::Finished | SecRunState::Cancelled => { | ||||
|                 return None; | ||||
|             } | ||||
|             SecRunState::Waiting => (None, None), | ||||
|             SecRunState::Running { start_time } => (Some(instant_to_string(start_time)), None), | ||||
|             SecRunState::Paused { | ||||
|                 start_time, | ||||
|                 pause_time, | ||||
|             } => ( | ||||
|                 Some(instant_to_string(start_time)), | ||||
|                 Some(instant_to_string(pause_time)), | ||||
|             ), | ||||
|         }; | ||||
|         Some(Self { | ||||
|             id: run.handle.clone().into_inner(), | ||||
|             section: run.section.id, | ||||
|             total_duration: run.total_duration.as_secs_f64(), | ||||
|             duration: run.duration.as_secs_f64(), | ||||
|             start_time, | ||||
|             pause_time, | ||||
|             unpause_time: None, // TODO: this is kinda useless, should probably be removed
 | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SecRunnerStateJson { | ||||
|     queue: Vec<SecRunJson>, | ||||
|     current: Option<SecRunJson>, | ||||
|     paused: bool, | ||||
| } | ||||
| 
 | ||||
| impl From<&SecRunnerState> for SecRunnerStateJson { | ||||
|     fn from(state: &SecRunnerState) -> Self { | ||||
|         let mut run_queue = state.run_queue.iter(); | ||||
|         let current = run_queue.next().and_then(|run| SecRunJson::from_run(run)); | ||||
|         let queue = run_queue | ||||
|             .filter_map(|run| SecRunJson::from_run(run)) | ||||
|             .collect(); | ||||
|         Self { | ||||
|             queue, | ||||
|             current, | ||||
|             paused: state.paused, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,19 +1,20 @@ | ||||
| use crate::{ | ||||
|     mqtt_interface::MqttInterface, | ||||
|     program_runner::{ProgramEvent, ProgramEventRecv}, | ||||
|     section_runner::{SectionEvent, SectionEventRecv}, | ||||
|     section_runner::{SecRunnerState, SecRunnerStateRecv, SectionEvent, SectionEventRecv}, | ||||
| }; | ||||
| use tokio::{ | ||||
|     select, | ||||
|     sync::{broadcast, oneshot}, | ||||
|     task::JoinHandle, | ||||
| }; | ||||
| use tracing::{trace, trace_span, warn}; | ||||
| use tracing::{trace_span, warn}; | ||||
| use tracing_futures::Instrument; | ||||
| 
 | ||||
| struct UpdateListenerTask { | ||||
|     section_events: SectionEventRecv, | ||||
|     program_events: ProgramEventRecv, | ||||
|     sec_runner_state: SecRunnerStateRecv, | ||||
|     mqtt_interface: MqttInterface, | ||||
|     running: bool, | ||||
| } | ||||
| @ -26,7 +27,7 @@ impl UpdateListenerTask { | ||||
|         let event = match event { | ||||
|             Ok(ev) => ev, | ||||
|             Err(broadcast::RecvError::Closed) => { | ||||
|                 trace!("section events channel closed"); | ||||
|                 warn!("section events channel closed"); | ||||
|                 self.running = false; | ||||
|                 return Ok(()); | ||||
|             } | ||||
| @ -58,12 +59,12 @@ impl UpdateListenerTask { | ||||
|         let event = match event { | ||||
|             Ok(ev) => ev, | ||||
|             Err(broadcast::RecvError::Closed) => { | ||||
|                 trace!("section events channel closed"); | ||||
|                 warn!("program events channel closed"); | ||||
|                 self.running = false; | ||||
|                 return Ok(()); | ||||
|             } | ||||
|             Err(broadcast::RecvError::Lagged(n)) => { | ||||
|                 warn!("section events lagged by {}", n); | ||||
|                 warn!("program events lagged by {}", n); | ||||
|                 return Ok(()); | ||||
|             } | ||||
|         }; | ||||
| @ -77,6 +78,19 @@ impl UpdateListenerTask { | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn handle_sec_runner_state(&mut self, state: Option<SecRunnerState>) -> eyre::Result<()> { | ||||
|         let state = match state { | ||||
|             Some(state) => state, | ||||
|             None => { | ||||
|                 warn!("section runner events channel closed"); | ||||
|                 self.running = false; | ||||
|                 return Ok(()); | ||||
|             } | ||||
|         }; | ||||
|         self.mqtt_interface.publish_section_runner(&state).await?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn run_impl(&mut self) -> eyre::Result<()> { | ||||
|         while self.running { | ||||
|             select! { | ||||
| @ -86,6 +100,9 @@ impl UpdateListenerTask { | ||||
|                 program_event = self.program_events.recv() => { | ||||
|                     self.handle_program_event(program_event).await? | ||||
|                 } | ||||
|                 sec_runner_state = self.sec_runner_state.recv() => { | ||||
|                     self.handle_sec_runner_state(sec_runner_state).await? | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|         Ok(()) | ||||
| @ -122,11 +139,13 @@ impl UpdateListener { | ||||
|     pub fn start( | ||||
|         section_events: SectionEventRecv, | ||||
|         program_events: ProgramEventRecv, | ||||
|         sec_runner_state: SecRunnerStateRecv, | ||||
|         mqtt_interface: MqttInterface, | ||||
|     ) -> Self { | ||||
|         let task = UpdateListenerTask { | ||||
|             section_events, | ||||
|             program_events, | ||||
|             sec_runner_state, | ||||
|             mqtt_interface, | ||||
|             running: true, | ||||
|         }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user