Persists the tokens-saved counter, flushes pending tokens to the worldwide counter, checkpoints the WAL, and logs a session summary. Idempotent — safe to call multiple times. `run` invokes it once when its main loop exits; callers (e.g. `main.rs`, tests) may invoke it explicitly afterwards without re-running the persistence logic.
(&self)
| 1834 | /// its main loop exits; callers (e.g. `main.rs`, tests) may invoke it |
| 1835 | /// explicitly afterwards without re-running the persistence logic. |
| 1836 | pub async fn shutdown(&self) { |
| 1837 | // Idempotency guard: only run the persistence path once. |
| 1838 | if self.shutdown_done.swap(true, Ordering::SeqCst) { |
| 1839 | return; |
| 1840 | } |
| 1841 | |
| 1842 | let uptime = self.stats.started_at.elapsed(); |
| 1843 | let tool_calls = self.stats.tool_calls.load(Ordering::Relaxed); |
| 1844 | let tokens_saved = self.tokens_saved.load(Ordering::Relaxed); |
| 1845 | |
| 1846 | let cg = self.cg_snapshot().await; |
| 1847 | // Persist final tokens-saved value |
| 1848 | if let Err(e) = cg.set_tokens_saved(tokens_saved).await { |
| 1849 | eprintln!("[tracedecay] warning: failed to persist tokens_saved on shutdown: {e}"); |
| 1850 | } |
| 1851 | |
| 1852 | // Update global DB with final count and checkpoint it |
| 1853 | if let Some(ref gdb) = self.global_db { |
| 1854 | gdb.upsert(cg.project_root(), tokens_saved).await; |
| 1855 | gdb.checkpoint().await; |
| 1856 | } |
| 1857 | |
| 1858 | // Flush remaining delta to worldwide counter (what periodic flushes missed) |
| 1859 | let last_flushed = self.last_flushed_tokens.load(Ordering::Relaxed); |
| 1860 | if self.global_db.is_some() && tokens_saved > last_flushed { |
| 1861 | let delta = tokens_saved - last_flushed; |
| 1862 | let mut config = crate::user_config::UserConfig::load(); |
| 1863 | config.pending_upload += delta; |
| 1864 | if config.upload_enabled { |
| 1865 | if let Some(_total) = crate::cloud::flush_pending(config.pending_upload) { |
| 1866 | config.pending_upload = 0; |
| 1867 | let now = std::time::SystemTime::now() |
| 1868 | .duration_since(std::time::UNIX_EPOCH) |
| 1869 | .unwrap_or_default() |
| 1870 | .as_secs() as i64; |
| 1871 | config.last_upload_at = now; |
| 1872 | } |
| 1873 | } |
| 1874 | config.save(); |
| 1875 | } |
| 1876 | |
| 1877 | // Checkpoint WAL to merge it into the main database file |
| 1878 | if let Err(e) = cg.checkpoint().await { |
| 1879 | eprintln!("[tracedecay] warning: failed to checkpoint WAL on shutdown: {e}"); |
| 1880 | } |
| 1881 | |
| 1882 | eprintln!( |
| 1883 | "[tracedecay] shutdown: {} tool calls, ~{} tokens saved, uptime {}s", |
| 1884 | tool_calls, |
| 1885 | tokens_saved, |
| 1886 | uptime.as_secs() |
| 1887 | ); |
| 1888 | } |
| 1889 | |
| 1890 | /// Dispatches a parsed JSON-RPC request to the appropriate handler. |
| 1891 | /// |