1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::{Arc, RwLock, Weak};
4use std::time::Duration;
5
6use sofie_core::{Error, Fnv1a64, Result, Timestamp, unpoisoned};
7use sofie_db::{Channel, Db, Listening, Select};
8
9use crate::store::{self, StoredFlag, StoredSetting};
10use crate::{DeclaredSetting, Features, HeldSetting, Rule, SettingValue, Subject};
11
12const EXPIRY: Duration = Duration::from_secs(30);
14const SETTLE: Duration = Duration::from_millis(200);
16
17#[derive(Clone)]
19pub struct Flags {
20 shared: Arc<Shared>,
21}
22
23struct Shared {
24 db: Db,
25 environment: Arc<str>,
26 held: RwLock<Arc<Snapshot>>,
27 reloads: AtomicU64,
28}
29
30#[derive(Clone, Default)]
31pub(crate) struct Snapshot {
32 pub(crate) rules: HashMap<String, Rule>,
33 changed: HashMap<String, Timestamp>,
34 pub(crate) settings: HashMap<String, SettingValue>,
35 declared: Arc<[DeclaredSetting]>,
36 pub(crate) version: u64,
37 reload: u64,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Settled {
43 pub name: String,
44 pub on: bool,
45 pub since: Timestamp,
46}
47
48impl Flags {
49 pub async fn start(db: &Db, environment: &str) -> Result<Self> {
51 let listening = Listening::start(db.clone(), Channel::Messages, EXPIRY).await;
52 let flags = Self {
53 shared: Arc::new(Shared {
54 db: db.clone(),
55 environment: Arc::from(environment),
56 held: RwLock::default(),
57 reloads: AtomicU64::new(0),
58 }),
59 };
60 flags.shared.reload().await?;
61 tokio::spawn(keep_fresh(Arc::downgrade(&flags.shared), listening));
62 Ok(flags)
63 }
64
65 #[must_use]
66 pub fn evaluate(&self, subject: Subject) -> Features {
67 Features::new(self.held(), Arc::clone(&self.shared.environment), subject)
68 }
69
70 pub async fn set(&self, name: &str, rule: Rule) -> Result<()> {
71 store::save_flag(&self.shared.db, name, rule).await?;
72 self.shared.reload().await
73 }
74
75 pub fn declare_settings(&self, declared: Vec<DeclaredSetting>) -> Result<()> {
77 for (index, setting) in declared.iter().enumerate() {
78 store::refuse_malformed_name("setting", &setting.name)?;
79 if declared[..index]
80 .iter()
81 .any(|earlier| earlier.name == setting.name)
82 {
83 return Err(Error::internal_message(format!(
84 "setting `{}` is declared twice",
85 setting.name
86 )));
87 }
88 }
89 let mut held = unpoisoned::write(&self.shared.held);
90 *held = Arc::new(Snapshot {
91 declared: Arc::from(declared),
92 ..Snapshot::clone(&held)
93 });
94 Ok(())
95 }
96
97 pub async fn change_setting(&self, name: &str, text: &str) -> Result<()> {
98 let default = self
99 .held()
100 .declared
101 .iter()
102 .find(|declared| declared.name == name)
103 .map(|declared| declared.default.clone())
104 .ok_or_else(|| {
105 Error::invalid("setting", format!("`{name}` is not a declared setting"))
106 })?;
107 store::save_setting(&self.shared.db, name, default.parse_like(text)?).await?;
108 self.shared.reload().await
109 }
110
111 #[must_use]
112 pub fn rules(&self) -> Vec<(String, Rule)> {
113 let mut rules: Vec<(String, Rule)> = self
114 .held()
115 .rules
116 .iter()
117 .map(|(name, rule)| (name.clone(), rule.clone()))
118 .collect();
119 rules.sort_by(|left, right| left.0.cmp(&right.0));
120 rules
121 }
122
123 #[must_use]
124 pub fn settled(&self) -> Vec<Settled> {
125 let held = self.held();
126 let mut settled: Vec<Settled> = held
127 .rules
128 .iter()
129 .filter(|(_, rule)| rule.is_settled())
130 .filter_map(|(name, rule)| {
131 Some(Settled {
132 name: name.clone(),
133 on: rule.on,
134 since: *held.changed.get(name)?,
135 })
136 })
137 .collect();
138 settled.sort_by(|left, right| left.name.cmp(&right.name));
139 settled
140 }
141
142 fn held(&self) -> Arc<Snapshot> {
143 Arc::clone(&unpoisoned::read(&self.shared.held))
144 }
145}
146
147impl Shared {
148 async fn reload(&self) -> Result<()> {
149 let reload = self.reloads.fetch_add(1, Ordering::Relaxed) + 1;
150 let read = self.db.read();
151 let flags = StoredFlag::query().all(&read).await?;
152 let settings = StoredSetting::query().all(&read).await?;
153 install(&self.held, reload, |declared| {
154 snapshot_of(flags, settings, declared)
155 });
156 Ok(())
157 }
158}
159
160fn install(
163 held: &RwLock<Arc<Snapshot>>,
164 reload: u64,
165 build: impl FnOnce(Arc<[DeclaredSetting]>) -> Snapshot,
166) {
167 let mut held = unpoisoned::write(held);
168 if held.reload > reload {
169 return;
170 }
171 let snapshot = Snapshot {
172 reload,
173 ..build(Arc::clone(&held.declared))
174 };
175 *held = Arc::new(snapshot);
176}
177
178fn snapshot_of(
179 flags: Vec<StoredFlag>,
180 settings: Vec<StoredSetting>,
181 declared: Arc<[DeclaredSetting]>,
182) -> Snapshot {
183 let mut snapshot = Snapshot {
184 declared,
185 ..Snapshot::default()
186 };
187 for flag in flags {
188 let rule = match Rule::from_json(&flag.name, &flag.json) {
189 Ok(rule) => rule,
190 Err(error) => {
191 tracing::warn!(flag = %flag.name, error = %error, "a stored flag is malformed; it stays off");
192 continue;
193 }
194 };
195 snapshot.changed.insert(flag.name.clone(), flag.changed_at);
196 snapshot.rules.insert(flag.name, rule);
197 }
198 for setting in settings {
199 let value = match SettingValue::from_json(&setting.name, &setting.json) {
200 Ok(value) => value,
201 Err(error) => {
202 tracing::warn!(setting = %setting.name, error = %error, "a stored setting is malformed; using its default");
203 continue;
204 }
205 };
206 let redeclared = snapshot
207 .declared
208 .iter()
209 .any(|declared| declared.name == setting.name && !declared.default.same_kind(&value));
210 if redeclared {
211 tracing::warn!(
212 setting = %setting.name,
213 "the stored value is of another kind than the setting's declaration; using its default"
214 );
215 }
216 snapshot.settings.insert(setting.name, value);
217 }
218 snapshot.version = version_of(&snapshot);
219 snapshot
220}
221
222fn version_of(snapshot: &Snapshot) -> u64 {
224 let mut entries: Vec<(&String, String)> = snapshot
225 .rules
226 .iter()
227 .map(|(name, rule)| (name, rule.to_json()))
228 .chain(
229 snapshot
230 .settings
231 .iter()
232 .map(|(name, value)| (name, value.to_json())),
233 )
234 .collect();
235 entries.sort();
236 entries
237 .into_iter()
238 .fold(Fnv1a64::new(), |hash, (name, stored)| {
239 hash.write(name.as_bytes())
240 .write(&[0])
241 .write(stored.as_bytes())
242 .write(&[0])
243 })
244 .finish()
245}
246
247impl Snapshot {
248 pub(crate) fn held_settings(&self) -> Vec<HeldSetting> {
249 let mut held: Vec<HeldSetting> = self
250 .declared
251 .iter()
252 .map(|declared| HeldSetting {
253 name: declared.name.clone(),
254 value: self
255 .settings
256 .get(&declared.name)
257 .filter(|stored| stored.same_kind(&declared.default))
258 .unwrap_or(&declared.default)
259 .clone(),
260 default: declared.default.clone(),
261 })
262 .collect();
263 held.sort_by(|left, right| left.name.cmp(&right.name));
264 held
265 }
266}
267
268async fn keep_fresh(shared: Weak<Shared>, mut listening: Listening) {
270 loop {
271 listening.changed().await;
272 tokio::time::sleep(SETTLE).await;
273 let Some(shared) = shared.upgrade() else {
274 return;
275 };
276 if let Err(error) = shared.reload().await {
277 tracing::warn!(error = %error, "reading the flags failed; keeping the last ones read");
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 fn read_with(version: u64) -> impl FnOnce(Arc<[DeclaredSetting]>) -> Snapshot {
287 move |declared| Snapshot {
288 declared,
289 version,
290 ..Snapshot::default()
291 }
292 }
293
294 #[test]
295 fn a_reload_that_started_earlier_does_not_replace_a_later_one() {
296 let held = RwLock::default();
297
298 install(&held, 2, read_with(20));
299 install(&held, 1, read_with(10));
300
301 assert_eq!(held.read().unwrap().version, 20);
302 }
303}