1use std::collections::HashMap;
5use std::sync::{Mutex, RwLock};
6use std::time::{Duration, Instant};
7
8use async_trait::async_trait;
9use chrono::{SecondsFormat, Utc};
10use sea_query::{Expr, Iden, Query};
11
12use super::Signals;
13use crate::error::CoreResult;
14use crate::query::{bind_values, build, on_conflict_ignore, AnyRowExt};
15use crate::Db;
16use sqlx::Row;
17
18fn seed() -> u64 {
21 u64::from(rand::random::<u32>()) + 1
22}
23
24#[derive(Default)]
26pub struct LocalSignals {
27 generations: RwLock<HashMap<String, u64>>,
28 locks: Mutex<HashMap<String, (String, Instant)>>,
29}
30
31impl LocalSignals {
32 pub fn new() -> Self {
33 Self::default()
34 }
35}
36
37#[async_trait]
38impl Signals for LocalSignals {
39 fn name(&self) -> &'static str {
40 "local"
41 }
42 fn current(&self, namespace: &str) -> Option<u64> {
43 self.generations
44 .read()
45 .unwrap_or_else(|e| e.into_inner())
46 .get(namespace)
47 .copied()
48 }
49 async fn ensure(&self, namespace: &str) -> CoreResult<u64> {
50 let mut all = self.generations.write().unwrap_or_else(|e| e.into_inner());
51 Ok(*all.entry(namespace.to_string()).or_insert_with(seed))
52 }
53 async fn bump(&self, namespace: &str) -> CoreResult<u64> {
54 let mut all = self.generations.write().unwrap_or_else(|e| e.into_inner());
55 let next = all.entry(namespace.to_string()).or_insert_with(seed);
56 *next += 1;
57 Ok(*next)
58 }
59 async fn refresh(&self) -> CoreResult<()> {
60 Ok(())
61 }
62 async fn known(&self) -> CoreResult<Vec<(String, u64)>> {
63 let all = self.generations.read().unwrap_or_else(|e| e.into_inner());
64 let mut out: Vec<(String, u64)> = all.iter().map(|(k, v)| (k.clone(), *v)).collect();
65 out.sort();
66 Ok(out)
67 }
68 async fn lock(&self, name: &str, owner: &str, ttl: Duration) -> CoreResult<bool> {
69 let mut locks = self.locks.lock().unwrap_or_else(|e| e.into_inner());
70 let now = Instant::now();
71 if locks.get(name).is_some_and(|(_, until)| *until > now) {
72 return Ok(false);
73 }
74 locks.insert(name.to_string(), (owner.to_string(), now + ttl));
75 Ok(true)
76 }
77 async fn unlock(&self, name: &str, owner: &str) -> CoreResult<bool> {
78 let mut locks = self.locks.lock().unwrap_or_else(|e| e.into_inner());
79 if locks.get(name).is_some_and(|(held, _)| held == owner) {
80 locks.remove(name);
81 Ok(true)
82 } else {
83 Ok(false)
84 }
85 }
86}
87
88#[derive(Iden)]
89pub(crate) enum CacheGenerations {
90 #[iden = "cache_generations"]
91 Table,
92 Namespace,
93 Generation,
94}
95
96#[derive(Iden)]
97pub(crate) enum CacheLocks {
98 #[iden = "cache_locks"]
99 Table,
100 Name,
101 Owner,
102 ExpiresAt,
103}
104
105fn now_text() -> String {
106 Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true)
107}
108
109fn until_text(ttl: Duration) -> String {
110 let ttl = chrono::Duration::from_std(ttl).unwrap_or(chrono::Duration::MAX);
111 (Utc::now() + ttl).to_rfc3339_opts(SecondsFormat::Micros, true)
112}
113
114pub struct DbSignals {
117 db: Db,
118 replica: RwLock<HashMap<String, u64>>,
119}
120
121impl DbSignals {
122 pub fn new(db: Db) -> Self {
123 Self {
124 db,
125 replica: RwLock::new(HashMap::new()),
126 }
127 }
128
129 fn remember(&self, namespace: &str, generation: u64) {
130 self.replica
131 .write()
132 .unwrap_or_else(|e| e.into_inner())
133 .insert(namespace.to_string(), generation);
134 }
135
136 async fn read(&self, namespace: &str) -> CoreResult<Option<u64>> {
137 let (sql, values) = build(
138 self.db.backend,
139 Query::select()
140 .column(CacheGenerations::Generation)
141 .from(CacheGenerations::Table)
142 .and_where(Expr::col(CacheGenerations::Namespace).eq(namespace))
143 .to_owned(),
144 );
145 let row = bind_values(sqlx::query(&sql), values)
146 .fetch_optional(&self.db.pool)
147 .await?;
148 Ok(match row {
149 Some(r) => Some(r.try_get::<i64, _>("generation")?.max(0) as u64),
150 None => None,
151 })
152 }
153
154 async fn insert_if_new(&self, namespace: &str) -> CoreResult<()> {
155 let (sql, values) = build(
156 self.db.backend,
157 Query::insert()
158 .into_table(CacheGenerations::Table)
159 .columns([CacheGenerations::Namespace, CacheGenerations::Generation])
160 .values_panic([namespace.into(), (seed() as i64).into()])
161 .on_conflict(on_conflict_ignore([CacheGenerations::Namespace]))
162 .to_owned(),
163 );
164 bind_values(sqlx::query(&sql), values)
165 .execute(&self.db.pool)
166 .await?;
167 Ok(())
168 }
169}
170
171#[async_trait]
172impl Signals for DbSignals {
173 fn name(&self) -> &'static str {
174 "database"
175 }
176 fn current(&self, namespace: &str) -> Option<u64> {
177 self.replica
178 .read()
179 .unwrap_or_else(|e| e.into_inner())
180 .get(namespace)
181 .copied()
182 }
183 async fn ensure(&self, namespace: &str) -> CoreResult<u64> {
184 if let Some(current) = self.current(namespace) {
185 return Ok(current);
186 }
187 let generation = match self.read(namespace).await? {
188 Some(generation) => generation,
189 None => {
190 self.insert_if_new(namespace).await?;
191 self.read(namespace).await?.unwrap_or_else(seed)
192 }
193 };
194 self.remember(namespace, generation);
195 Ok(generation)
196 }
197 async fn bump(&self, namespace: &str) -> CoreResult<u64> {
198 self.insert_if_new(namespace).await?;
199 let (sql, values) = build(
200 self.db.backend,
201 Query::update()
202 .table(CacheGenerations::Table)
203 .value(
204 CacheGenerations::Generation,
205 Expr::col(CacheGenerations::Generation).add(1),
206 )
207 .and_where(Expr::col(CacheGenerations::Namespace).eq(namespace))
208 .to_owned(),
209 );
210 bind_values(sqlx::query(&sql), values)
211 .execute(&self.db.pool)
212 .await?;
213 let generation = self.read(namespace).await?.unwrap_or_else(seed);
214 self.remember(namespace, generation);
215 Ok(generation)
216 }
217 async fn refresh(&self) -> CoreResult<()> {
218 let known = self.known().await?;
219 let mut replica = self.replica.write().unwrap_or_else(|e| e.into_inner());
220 for (namespace, generation) in known {
221 replica.insert(namespace, generation);
222 }
223 Ok(())
224 }
225 async fn known(&self) -> CoreResult<Vec<(String, u64)>> {
226 let (sql, values) = build(
227 self.db.backend,
228 Query::select()
229 .columns([CacheGenerations::Namespace, CacheGenerations::Generation])
230 .from(CacheGenerations::Table)
231 .order_by(CacheGenerations::Namespace, sea_query::Order::Asc)
232 .to_owned(),
233 );
234 let rows = bind_values(sqlx::query(&sql), values)
235 .fetch_all(&self.db.pool)
236 .await?;
237 let mut out = Vec::with_capacity(rows.len());
238 for r in rows {
239 out.push((
240 r.get_text("namespace")?,
241 r.try_get::<i64, _>("generation")?.max(0) as u64,
242 ));
243 }
244 Ok(out)
245 }
246 async fn lock(&self, name: &str, owner: &str, ttl: Duration) -> CoreResult<bool> {
247 let (sql, values) = build(
250 self.db.backend,
251 Query::delete()
252 .from_table(CacheLocks::Table)
253 .and_where(Expr::col(CacheLocks::Name).eq(name))
254 .and_where(Expr::col(CacheLocks::ExpiresAt).lt(now_text()))
255 .to_owned(),
256 );
257 bind_values(sqlx::query(&sql), values)
258 .execute(&self.db.pool)
259 .await?;
260 let (sql, values) = build(
261 self.db.backend,
262 Query::insert()
263 .into_table(CacheLocks::Table)
264 .columns([CacheLocks::Name, CacheLocks::Owner, CacheLocks::ExpiresAt])
265 .values_panic([name.into(), owner.into(), until_text(ttl).into()])
266 .on_conflict(on_conflict_ignore([CacheLocks::Name]))
267 .to_owned(),
268 );
269 bind_values(sqlx::query(&sql), values)
270 .execute(&self.db.pool)
271 .await?;
272 let (sql, values) = build(
273 self.db.backend,
274 Query::select()
275 .column(CacheLocks::Owner)
276 .from(CacheLocks::Table)
277 .and_where(Expr::col(CacheLocks::Name).eq(name))
278 .to_owned(),
279 );
280 let row = bind_values(sqlx::query(&sql), values)
281 .fetch_optional(&self.db.pool)
282 .await?;
283 Ok(match row {
284 Some(r) => r.get_text("owner")? == owner,
285 None => false,
286 })
287 }
288 async fn unlock(&self, name: &str, owner: &str) -> CoreResult<bool> {
289 let (sql, values) = build(
290 self.db.backend,
291 Query::delete()
292 .from_table(CacheLocks::Table)
293 .and_where(Expr::col(CacheLocks::Name).eq(name))
294 .and_where(Expr::col(CacheLocks::Owner).eq(owner))
295 .to_owned(),
296 );
297 let done = bind_values(sqlx::query(&sql), values)
298 .execute(&self.db.pool)
299 .await?;
300 Ok(done.rows_affected() > 0)
301 }
302}