From: Simon Tatham Date: Sat, 6 Jan 2024 14:16:29 +0000 (+0000) Subject: Checkpoint the LDB to disk every few minutes. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=0de6ad9421fa27a13efb7ddb4e502ae1b5323011;p=mastodonochrome.git Checkpoint the LDB to disk every few minutes. That should do a decent job of defending against data loss from panics, SIGTERM, machine reboots, etc. --- diff --git a/TODO.md b/TODO.md index 6e9c965..ca2fefc 100644 --- a/TODO.md +++ b/TODO.md @@ -32,12 +32,6 @@ Each of those should instead catch the error, make an Error Log record, and transfer you to the error-log viewer with a beep, just the way Mono did it. -## LDB checkpointing - -It would be nice to checkpoint the LDB to disk every few minutes, if -it's changed, as a precaution against data loss if the client crashes -after sitting in the same `File` for a long time. - ## Reading ### Sensitive-content markers diff --git a/src/client.rs b/src/client.rs index b93ad45..0e16783 100644 --- a/src/client.rs +++ b/src/client.rs @@ -41,7 +41,7 @@ pub enum StreamResponse { EOF, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct StreamUpdate { pub id: StreamId, pub response: StreamResponse, diff --git a/src/tui.rs b/src/tui.rs index 296bd9a..70f39c5 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -14,6 +14,7 @@ use std::cmp::min; use std::collections::{BTreeMap, HashMap, HashSet, hash_map}; use std::io::{Stdout, Write, stdout}; use std::fs::File; +use std::time::Duration; use unicode_width::UnicodeWidthStr; use super::activity_stack::*; @@ -129,10 +130,11 @@ fn ratatui_set_string(buf: &mut Buffer, x: usize, y: usize, } } -#[derive(Debug)] +#[derive(Debug, Clone)] enum SubthreadEvent { TermEv(Event), StreamEv(StreamUpdate), + LDBCheckpointTimer, } pub enum PhysicalAction { @@ -309,20 +311,9 @@ impl Tui { } fn run_inner(&mut self) -> Result<(), TuiError> { - { - let sender = self.subthread_sender.clone(); - self.client.start_streaming_thread( - &StreamId::User, Box::new(move |update| { - if sender.send( - SubthreadEvent::StreamEv(update)).is_err() { - // It would be nice to do something about this - // error, but what _can_ we do? We can hardly send - // an error notification back to the main thread, - // because that communication channel is just what - // we've had a problem with. - } - }))?; - } + self.start_streaming_subthread(StreamId::User)?; + self.start_timing_subthread(Duration::from_secs(120), + SubthreadEvent::LDBCheckpointTimer)?; // Now fetch the two basic feeds - home and mentions. Most // importantly, getting the mentions feed started means that @@ -336,6 +327,39 @@ impl Tui { self.main_loop() } + fn start_streaming_subthread(&mut self, id: StreamId) + -> Result<(), TuiError> + { + let sender = self.subthread_sender.clone(); + self.client.start_streaming_thread( + &id, Box::new(move |update| { + if sender.send( + SubthreadEvent::StreamEv(update).clone()).is_err() { + // It would be nice to do something about this + // error, but what _can_ we do? We can hardly send + // an error notification back to the main thread, + // because that communication channel is just what + // we've had a problem with. + } + }))?; + + Ok(()) + } + + fn start_timing_subthread(&mut self, dur: Duration, ev: SubthreadEvent) + -> Result<(), TuiError> + { + let sender = self.subthread_sender.clone(); + let _joinhandle = std::thread::spawn(move || { + loop { + std::thread::sleep(dur); + // Similarly ignore the error (see above) + if sender.send(ev.clone()).is_err() {} + } + }); + Ok(()) + } + fn main_loop(&mut self) -> Result<(), TuiError> { 'outer: loop { let state = &mut self.state; @@ -406,6 +430,8 @@ impl Tui { _ => (), } } + Ok(SubthreadEvent::LDBCheckpointTimer) => + state.checkpoint_ldb(), } } } @@ -648,8 +674,7 @@ impl TuiLogicalState { } } - fn changed_activity(&mut self, client: &mut Client, post: Option, - is_interrupt: bool) { + fn checkpoint_ldb(&mut self) { if let Some((feed_id, saved_pos)) = self.activity_state.save_file_position() { @@ -661,6 +686,11 @@ impl TuiLogicalState { self.save_ldb().unwrap(); } } + } + + fn changed_activity(&mut self, client: &mut Client, post: Option, + is_interrupt: bool) { + self.checkpoint_ldb(); self.activity_state = self.new_activity_state( self.activity_stack.top(), client, post, is_interrupt); self.overlay_activity_state = match self.activity_stack.overlay() {