Alex Mikhalev
4 years ago
11 changed files with 264 additions and 44 deletions
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
use sprinklers_actors::{state_manager, StateManager}; |
||||
use sprinklers_database::{self as database, DbConn}; |
||||
|
||||
use eyre::{eyre, WrapErr}; |
||||
use sprinklers_core::model::{ProgramRef, Programs}; |
||||
use tokio::{ |
||||
runtime, |
||||
sync::{mpsc, watch}, |
||||
}; |
||||
use tracing::warn; |
||||
|
||||
pub struct StateManagerThread { |
||||
db_conn: DbConn, |
||||
request_rx: mpsc::Receiver<state_manager::Request>, |
||||
programs_tx: watch::Sender<Programs>, |
||||
} |
||||
|
||||
struct State { |
||||
programs: Programs, |
||||
} |
||||
|
||||
impl StateManagerThread { |
||||
pub fn start(db_conn: DbConn) -> StateManager { |
||||
let (request_tx, request_rx) = mpsc::channel(8); |
||||
let (programs_tx, programs_rx) = watch::channel(Programs::default()); |
||||
let task = StateManagerThread { |
||||
db_conn, |
||||
request_rx, |
||||
programs_tx, |
||||
}; |
||||
let runtime_handle = runtime::Handle::current(); |
||||
std::thread::Builder::new() |
||||
.name("sprinklers_rs::state_manager".into()) |
||||
.spawn(move || task.run(runtime_handle)) |
||||
.expect("could not start state_manager thread"); |
||||
StateManager::new(request_tx, programs_rx) |
||||
} |
||||
|
||||
fn broadcast_programs(&mut self, programs: Programs) { |
||||
if let Err(err) = self.programs_tx.broadcast(programs) { |
||||
warn!("could not broadcast programs: {}", err); |
||||
} |
||||
} |
||||
|
||||
fn handle_request( |
||||
&mut self, |
||||
request: state_manager::Request, |
||||
state: &mut State, |
||||
) -> eyre::Result<()> { |
||||
use state_manager::Request; |
||||
|
||||
match request { |
||||
Request::UpdateProgram { |
||||
id, |
||||
update, |
||||
resp_tx, |
||||
} => { |
||||
// HACK: would really like stable try notation
|
||||
let res = (|| -> state_manager::Result<ProgramRef> { |
||||
let mut trans = self |
||||
.db_conn |
||||
.transaction() |
||||
.wrap_err("failed to start transaction")?; |
||||
database::update_program(&mut trans, id, &update).map_err(|err| { |
||||
if let Some(e) = err.downcast_ref::<database::NoSuchProgram>() { |
||||
state_manager::StateError::NoSuchProgram(e.0) |
||||
} else { |
||||
err.into() |
||||
} |
||||
})?; |
||||
let new_program: ProgramRef = database::query_program_by_id(&trans, id)?.into(); |
||||
state.programs.insert(new_program.id, new_program.clone()); |
||||
trans.commit().wrap_err("could not commit transaction")?; |
||||
self.broadcast_programs(state.programs.clone()); |
||||
Ok(new_program) |
||||
})(); |
||||
resp_tx |
||||
.send(res) |
||||
.map_err(|_| eyre!("could not respond to UpdateProgram"))?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn load_state(&mut self) -> eyre::Result<State> { |
||||
let programs = |
||||
database::query_programs(&self.db_conn).wrap_err("could not query programs")?; |
||||
|
||||
Ok(State { programs }) |
||||
} |
||||
|
||||
fn run(mut self, runtime_handle: runtime::Handle) { |
||||
let mut state = self.load_state().expect("could not load initial state"); |
||||
|
||||
self.broadcast_programs(state.programs.clone()); |
||||
|
||||
while let Some(request) = runtime_handle.block_on(self.request_rx.recv()) { |
||||
if let Err(err) = self.handle_request(request, &mut state) { |
||||
warn!("error handling request: {:?}", err); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue