m4a_agent/engine/
driver.rs1use std::sync::mpsc::{self, Receiver};
5use std::sync::Arc;
6use std::thread::{self, JoinHandle};
7use std::time::Duration;
8
9use mail4agent_messenger::{
10 HttpResponseDescriptor, MessengerCore, MessengerEvent, OutgoingRequest, OutgoingRequestKind, RecordCodec, RequestId,
11};
12
13use super::http::clip_public;
14use crate::backend::Backend;
15use crate::error::{AgentError, Result};
16
17struct SyncFlight {
18 id: RequestId,
19 rx: Receiver<std::result::Result<HttpResponseDescriptor, String>>,
20 _worker: JoinHandle<()>,
22}
23
24pub type Persist<C> = Box<dyn FnMut(&mut MessengerCore<C>) -> Result<()> + Send>;
26
27pub struct Driver<C: RecordCodec> {
28 pub core: MessengerCore<C>,
29 backend: Arc<dyn Backend>,
30 persist: Persist<C>,
31 flight: Option<SyncFlight>,
32 pub http_trace: Vec<(OutgoingRequestKind, u16)>,
34 pub security_alerts: Vec<String>,
36}
37
38pub fn sync_timeout_ms(request: &OutgoingRequest) -> u64 {
39 request.query.iter().find(|(n, _)| n == "timeout").and_then(|(_, v)| v.parse().ok()).unwrap_or(0)
40}
41
42impl<C: RecordCodec> Driver<C> {
43 pub fn new(core: MessengerCore<C>, backend: Arc<dyn Backend>, persist: Persist<C>) -> Self {
44 Self { core, backend, persist, flight: None, http_trace: Vec::new(), security_alerts: Vec::new() }
45 }
46
47 pub fn backend(&self) -> &Arc<dyn Backend> {
48 &self.backend
49 }
50
51 pub fn set_backend(&mut self, backend: Arc<dyn Backend>) {
53 self.backend = backend;
54 }
55
56 pub fn sync_inflight(&self) -> bool {
57 self.flight.is_some()
58 }
59
60 pub fn persist(&mut self) -> Result<()> {
61 (self.persist)(&mut self.core)
62 }
63
64 pub fn abandon_sync(&mut self, now_ms: i64) {
66 if let Some(f) = self.flight.take() {
67 self.core.on_transport_error(&f.id, now_ms);
68 }
69 }
70
71 fn note_security_events(&mut self, events: &[MessengerEvent]) {
72 for event in events {
73 if let MessengerEvent::DeviceKeyChanged { user_id, device_id } = event {
74 self.security_alerts.push(format!("{} reset its keys (device {}); trust cleared, room keys re-shared", user_id.as_str(), device_id.as_str()));
75 }
76 }
77 }
78
79 pub fn release_after_flush(&mut self, now_ms: i64) -> Result<Vec<OutgoingRequest>> {
80 self.persist()?;
81 let mut released = self.core.releasable_requests(now_ms);
82 if released.is_empty() {
83 self.persist()?;
84 released = self.core.releasable_requests(now_ms);
85 }
86 Ok(released)
87 }
88
89 pub fn roundtrip(&mut self, request: &OutgoingRequest, now_ms: i64) -> Result<()> {
90 match self.backend.execute(request) {
91 Ok(response) => {
92 self.http_trace.push((request.kind, response.status));
93 let events = self.core.on_response(request.id.clone(), response, now_ms);
94 self.note_security_events(&events);
95 Ok(())
96 }
97 Err(err) => {
98 self.core.on_transport_error(&request.id, now_ms);
99 Err(err)
100 }
101 }
102 }
103
104 pub fn spawn_sync(&mut self, request: OutgoingRequest) -> Result<()> {
105 if self.flight.is_some() {
106 self.core.on_transport_error(&request.id, 0);
107 return Err(AgentError::Transport("a sync was already on the wire".into()));
108 }
109 let backend = self.backend.frozen().unwrap_or_else(|| Arc::clone(&self.backend));
110 let id = request.id.clone();
111 let (tx, rx) = mpsc::channel();
112 let worker = thread::spawn(move || {
113 let result = backend.execute(&request).map_err(|e| match e {
114 AgentError::Transport(t) => t,
115 other => clip_public(other.to_string()),
116 });
117 let _ = tx.send(result);
118 });
119 self.flight = Some(SyncFlight { id, rx, _worker: worker });
120 Ok(())
121 }
122
123 fn drop_flight(&mut self, now_ms: i64, why: &str) -> AgentError {
124 if let Some(f) = self.flight.take() {
125 self.core.on_transport_error(&f.id, now_ms);
126 }
127 AgentError::Transport(why.to_string())
128 }
129
130 pub fn harvest_sync(&mut self, now_ms: i64, wait: bool) -> Result<bool> {
133 let Some(flight) = self.flight.as_ref() else { return Ok(false) };
134 let (received, blocked) = match flight.rx.try_recv() {
135 Ok(r) => (Some(r), false),
136 Err(mpsc::TryRecvError::Disconnected) => return Err(self.drop_flight(now_ms, "sync worker dropped")),
137 Err(mpsc::TryRecvError::Empty) if !wait => (None, false),
138 Err(mpsc::TryRecvError::Empty) => match flight.rx.recv_timeout(Duration::from_secs(45)) {
139 Ok(r) => (Some(r), true),
140 Err(mpsc::RecvTimeoutError::Timeout) => return Err(self.drop_flight(now_ms, "sync timed out")),
141 Err(mpsc::RecvTimeoutError::Disconnected) => return Err(self.drop_flight(now_ms, "sync worker dropped")),
142 },
143 };
144 let Some(result) = received else { return Ok(false) };
145 let flight = self.flight.take().expect("flight present");
146 match result {
147 Ok(response) => {
148 self.http_trace.push((OutgoingRequestKind::Sync, response.status));
149 let events = self.core.on_response(flight.id, response, now_ms);
150 self.note_security_events(&events);
151 }
152 Err(err) => {
153 self.core.on_transport_error(&flight.id, now_ms);
154 return Err(AgentError::Transport(err));
155 }
156 }
157 self.persist()?;
158 if let Some(err) = self.core.take_ingest_error() {
159 return Err(AgentError::Ingest(clip_public(err)));
160 }
161 Ok(blocked)
162 }
163
164 pub fn step(&mut self, now_ms: i64, wait_for_sync: bool, waited: &mut bool) -> Result<bool> {
168 let released = self.release_after_flush(now_ms)?;
169 if released.is_empty() {
170 return Ok(false);
171 }
172 let (syncs, others): (Vec<_>, Vec<_>) = released.into_iter().partition(|r| r.kind == OutgoingRequestKind::Sync);
173 for request in &syncs {
174 self.spawn_sync(request.clone())?;
175 }
176 for request in others {
177 self.roundtrip(&request, now_ms)?;
178 }
179 self.persist()?;
180 for request in &syncs {
181 let timeout = sync_timeout_ms(request);
182 if timeout == 0 || (wait_for_sync && !*waited) {
183 self.harvest_sync(now_ms, true)?;
184 if timeout != 0 {
185 *waited = true;
186 }
187 }
188 }
189 self.persist()?;
190 if let Some(err) = self.core.take_ingest_error() {
191 return Err(AgentError::Ingest(clip_public(err)));
192 }
193 Ok(true)
194 }
195}