1use std::collections::HashMap;
2use std::path::Path;
3use std::time::Instant;
4
5use anyhow::{Context, Result};
6use serde_json::Value;
7use tokio::sync::{broadcast, mpsc, oneshot};
8use tracing::{info, warn};
9
10use crate::cell::db::CellDb;
11use crate::model::event::{EventRecord, Message};
12use crate::model::state::{CellMeta, CellStatus, CheckpointRecord};
13
14pub enum ActorMessage {
15 AppendEvent {
16 turn_id: Option<String>,
17 event_type: String,
18 payload: Value,
19 reply: oneshot::Sender<Result<EventRecord>>,
20 },
21 AppendEventsBatch {
22 requests: Vec<crate::model::event::AppendEventRequest>,
23 reply: oneshot::Sender<Result<Vec<EventRecord>>>,
24 },
25 GetEvents {
26 since_seq: Option<i64>,
27 limit: Option<i64>,
28 reply: oneshot::Sender<Result<Vec<EventRecord>>>,
29 },
30 Export {
31 reply: oneshot::Sender<Result<crate::model::state::CellExport>>,
32 },
33 GetMessages {
34 reply: oneshot::Sender<Result<Vec<Message>>>,
35 },
36 GetMeta {
37 reply: oneshot::Sender<Result<CellMeta>>,
38 },
39 SetKV {
40 key: String,
41 value: Value,
42 reply: oneshot::Sender<Result<()>>,
43 },
44 GetKV {
45 key: String,
46 reply: oneshot::Sender<Result<Option<Value>>>,
47 },
48 ListKV {
49 reply: oneshot::Sender<Result<HashMap<String, Value>>>,
50 },
51 CreateCheckpoint {
52 label: String,
53 reply: oneshot::Sender<Result<CheckpointRecord>>,
54 },
55 RestoreCheckpoint {
56 checkpoint_id: String,
57 reply: oneshot::Sender<Result<i64>>,
58 },
59 CheckpointWal {
60 reply: oneshot::Sender<Result<()>>,
61 },
62 Backup {
63 reply: oneshot::Sender<Result<Vec<u8>>>,
64 },
65 Fence,
66 GetIdleDuration {
67 reply: oneshot::Sender<std::time::Duration>,
68 },
69 Shutdown {
70 reply: oneshot::Sender<()>,
71 },
72}
73
74#[derive(Clone)]
75pub struct CellHandle {
76 pub cell_id: String,
77 tx: mpsc::Sender<ActorMessage>,
78 event_bus: broadcast::Sender<EventRecord>,
79}
80
81impl CellHandle {
82 pub async fn append_event(
83 &self,
84 turn_id: Option<String>,
85 event_type: impl Into<String>,
86 payload: Value,
87 ) -> Result<EventRecord> {
88 let (reply, rx) = oneshot::channel();
89 self.tx
90 .send(ActorMessage::AppendEvent {
91 turn_id,
92 event_type: event_type.into(),
93 payload,
94 reply,
95 })
96 .await
97 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
98 rx.await
99 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
100 }
101
102 pub async fn append_events_batch(
103 &self,
104 requests: Vec<crate::model::event::AppendEventRequest>,
105 ) -> Result<Vec<EventRecord>> {
106 let (reply, rx) = oneshot::channel();
107 self.tx
108 .send(ActorMessage::AppendEventsBatch { requests, reply })
109 .await
110 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
111 rx.await
112 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
113 }
114
115 pub async fn export(&self) -> Result<crate::model::state::CellExport> {
116 let (reply, rx) = oneshot::channel();
117 self.tx
118 .send(ActorMessage::Export { reply })
119 .await
120 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
121 rx.await
122 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
123 }
124
125 pub async fn get_events(
126 &self,
127 since_seq: Option<i64>,
128 limit: Option<i64>,
129 ) -> Result<Vec<EventRecord>> {
130 let (reply, rx) = oneshot::channel();
131 self.tx
132 .send(ActorMessage::GetEvents {
133 since_seq,
134 limit,
135 reply,
136 })
137 .await
138 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
139 rx.await
140 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
141 }
142
143 pub async fn get_messages(&self) -> Result<Vec<Message>> {
144 let (reply, rx) = oneshot::channel();
145 self.tx
146 .send(ActorMessage::GetMessages { reply })
147 .await
148 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
149 rx.await
150 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
151 }
152
153 pub async fn get_meta(&self) -> Result<CellMeta> {
154 let (reply, rx) = oneshot::channel();
155 self.tx
156 .send(ActorMessage::GetMeta { reply })
157 .await
158 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
159 rx.await
160 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
161 }
162
163 pub async fn set_kv(&self, key: impl Into<String>, value: Value) -> Result<()> {
164 let (reply, rx) = oneshot::channel();
165 self.tx
166 .send(ActorMessage::SetKV {
167 key: key.into(),
168 value,
169 reply,
170 })
171 .await
172 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
173 rx.await
174 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
175 }
176
177 pub async fn get_kv(&self, key: impl Into<String>) -> Result<Option<Value>> {
178 let (reply, rx) = oneshot::channel();
179 self.tx
180 .send(ActorMessage::GetKV {
181 key: key.into(),
182 reply,
183 })
184 .await
185 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
186 rx.await
187 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
188 }
189
190 pub async fn list_kv(&self) -> Result<HashMap<String, Value>> {
191 let (reply, rx) = oneshot::channel();
192 self.tx
193 .send(ActorMessage::ListKV { reply })
194 .await
195 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
196 rx.await
197 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
198 }
199
200 pub async fn create_checkpoint(&self, label: impl Into<String>) -> Result<CheckpointRecord> {
201 let (reply, rx) = oneshot::channel();
202 self.tx
203 .send(ActorMessage::CreateCheckpoint {
204 label: label.into(),
205 reply,
206 })
207 .await
208 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
209 rx.await
210 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
211 }
212
213 pub async fn restore_checkpoint(&self, checkpoint_id: impl Into<String>) -> Result<i64> {
214 let (reply, rx) = oneshot::channel();
215 self.tx
216 .send(ActorMessage::RestoreCheckpoint {
217 checkpoint_id: checkpoint_id.into(),
218 reply,
219 })
220 .await
221 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
222 rx.await
223 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
224 }
225
226 pub async fn checkpoint_wal(&self) -> Result<()> {
227 let (reply, rx) = oneshot::channel();
228 self.tx
229 .send(ActorMessage::CheckpointWal { reply })
230 .await
231 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
232 rx.await
233 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
234 }
235
236 pub async fn backup(&self) -> Result<Vec<u8>> {
237 let (reply, rx) = oneshot::channel();
238 self.tx
239 .send(ActorMessage::Backup { reply })
240 .await
241 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
242 rx.await
243 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))?
244 }
245
246 pub async fn fence(&self) {
247 let _ = self.tx.send(ActorMessage::Fence).await;
248 }
249
250 pub async fn idle_duration(&self) -> Result<std::time::Duration> {
251 let (reply, rx) = oneshot::channel();
252 self.tx
253 .send(ActorMessage::GetIdleDuration { reply })
254 .await
255 .map_err(|_| anyhow::anyhow!("Cell actor mailbox closed"))?;
256 rx.await
257 .map_err(|_| anyhow::anyhow!("Cell actor dropped reply"))
258 }
259
260 pub async fn shutdown(&self) {
261 let (reply, rx) = oneshot::channel();
262 if self.tx.send(ActorMessage::Shutdown { reply }).await.is_ok() {
263 let _ = rx.await;
264 }
265 }
266
267 pub fn subscribe(&self) -> broadcast::Receiver<EventRecord> {
268 self.event_bus.subscribe()
269 }
270}
271
272pub struct CellActor {
273 cell_id: String,
274 db: CellDb,
275 event_bus: broadcast::Sender<EventRecord>,
276 rx: mpsc::Receiver<ActorMessage>,
277 last_active: Instant,
278}
279
280impl CellActor {
281 pub fn spawn(
283 cell_id: impl Into<String>,
284 db_path: impl AsRef<Path>,
285 initial_name: Option<String>,
286 ) -> Result<CellHandle> {
287 let cell_id = cell_id.into();
288 let db_path = db_path.as_ref().to_path_buf();
289 let (tx, rx) = mpsc::channel(128);
290 let (event_bus, _) = broadcast::channel(256);
291 let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(1);
292
293 let thread_cell_id = cell_id.clone();
294 let event_bus_for_actor = event_bus.clone();
295 let short: String = cell_id.chars().take(8).collect();
296 let thread_name = format!("cell-{short}");
297
298 std::thread::Builder::new()
299 .name(thread_name)
300 .spawn(
301 move || match CellDb::open(&thread_cell_id, &db_path, initial_name.as_deref()) {
302 Ok(db) => {
303 let actor = CellActor {
304 cell_id: thread_cell_id,
305 db,
306 event_bus: event_bus_for_actor,
307 rx,
308 last_active: Instant::now(),
309 };
310 let _ = ready_tx.send(Ok(()));
311 actor.run();
312 }
313 Err(e) => {
314 let _ = ready_tx.send(Err(e));
315 }
316 },
317 )
318 .context("failed to spawn cell actor thread")?;
319
320 ready_rx
321 .recv()
322 .context("cell actor thread dropped before ready")??;
323
324 Ok(CellHandle {
325 cell_id,
326 tx,
327 event_bus,
328 })
329 }
330
331 fn run(mut self) {
332 info!("CellActor [{}] spawned and running", self.cell_id);
333
334 while let Some(msg) = self.rx.blocking_recv() {
335 self.last_active = Instant::now();
336
337 match msg {
338 ActorMessage::AppendEvent {
339 turn_id,
340 event_type,
341 payload,
342 reply,
343 } => {
344 let res = self.db.append_event(turn_id, &event_type, payload);
345 if let Ok(ref record) = res {
346 let _ = self.event_bus.send(record.clone());
347 }
348 let _ = reply.send(res);
349 }
350 ActorMessage::AppendEventsBatch { requests, reply } => {
351 let res = self.db.append_events_batch(requests);
352 if let Ok(ref records) = res {
353 for rec in records {
354 let _ = self.event_bus.send(rec.clone());
355 }
356 }
357 let _ = reply.send(res);
358 }
359 ActorMessage::Export { reply } => {
360 let res = self.db.export_cell();
361 let _ = reply.send(res);
362 }
363 ActorMessage::GetEvents {
364 since_seq,
365 limit,
366 reply,
367 } => {
368 let res = self.db.get_events(since_seq, limit);
369 let _ = reply.send(res);
370 }
371 ActorMessage::GetMessages { reply } => {
372 let res = self.db.get_messages();
373 let _ = reply.send(res);
374 }
375 ActorMessage::GetMeta { reply } => {
376 let res = self.db.get_meta();
377 let _ = reply.send(res);
378 }
379 ActorMessage::SetKV { key, value, reply } => {
380 let res = self.db.set_kv(&key, &value);
381 let _ = reply.send(res);
382 }
383 ActorMessage::GetKV { key, reply } => {
384 let res = self.db.get_kv(&key);
385 let _ = reply.send(res);
386 }
387 ActorMessage::ListKV { reply } => {
388 let res = self.db.list_kv();
389 let _ = reply.send(res);
390 }
391 ActorMessage::CreateCheckpoint { label, reply } => {
392 let res = self.db.create_checkpoint(&label);
393 let _ = reply.send(res);
394 }
395 ActorMessage::RestoreCheckpoint {
396 checkpoint_id,
397 reply,
398 } => {
399 let res = self.db.restore_checkpoint(&checkpoint_id);
400 let _ = reply.send(res);
401 }
402 ActorMessage::CheckpointWal { reply } => {
403 let res = self.db.checkpoint_wal();
404 let _ = reply.send(res);
405 }
406 ActorMessage::Backup { reply } => {
407 let res = (|| {
408 self.db.checkpoint_wal()?;
409 std::fs::read(self.db.db_path()).map_err(Into::into)
410 })();
411 let _ = reply.send(res);
412 }
413 ActorMessage::Fence => {
414 warn!(
415 "CellActor [{}] fenced due to lease expiration or takeover",
416 self.cell_id
417 );
418 let _ = self.db.update_status(CellStatus::Suspended);
419 break;
420 }
421 ActorMessage::GetIdleDuration { reply } => {
422 let _ = reply.send(self.last_active.elapsed());
423 }
424 ActorMessage::Shutdown { reply } => {
425 info!("CellActor [{}] received shutdown signal", self.cell_id);
426 let _ = self.db.update_status(CellStatus::Suspended);
427 let _ = self.db.checkpoint_wal();
428 let _ = reply.send(());
429 break;
430 }
431 }
432 }
433
434 info!("CellActor [{}] terminated", self.cell_id);
435 }
436}