chiark / gitweb /
Checkpoint the LDB to disk every few minutes.
authorSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 14:16:29 +0000 (14:16 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 6 Jan 2024 14:16:29 +0000 (14:16 +0000)
That should do a decent job of defending against data loss from
panics, SIGTERM, machine reboots, etc.

TODO.md
src/client.rs
src/tui.rs

diff --git a/TODO.md b/TODO.md
index 6e9c9659acf26b845e0abc5e33ff8d1c23d017b5..ca2fefc58b017696cbd976e0927ad3368c1b4873 100644 (file)
--- 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
index b93ad451ead573547b3f5ef333b716f97428ebcf..0e1678356ac00e1b15989bcad8508d430895a13f 100644 (file)
@@ -41,7 +41,7 @@ pub enum StreamResponse {
     EOF,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct StreamUpdate {
     pub id: StreamId,
     pub response: StreamResponse,
index 296bd9a38fc8977b2dc3ef3dd6ce4d5e47a9029d..70f39c50d34a553cf770c79b24e8504ff0a6a208 100644 (file)
@@ -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<Post>,
-                        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<Post>,
+                        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() {