Alex Mikhalev
4 years ago
11 changed files with 264 additions and 44 deletions
@ -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