Skip to main content

sofie_flags/
flags.rs

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
12/// Fallback re-read should a change's wake-up be lost.
13const EXPIRY: Duration = Duration::from_secs(30);
14/// A burst of wake-ups (every committed event sends one) is read once.
15const SETTLE: Duration = Duration::from_millis(200);
16
17/// A request reads what is held and never waits for a refresh.
18#[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    /// Which reload read it, so a slower reload that started earlier cannot replace it.
38    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    /// Listens before it reads, so a change committed once this returns always wakes it.
50    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    /// Replaces those declared before.
76    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
160/// Under the lock, so declarations made while the tables were read are kept. A reload that
161/// started later read the tables later, so an earlier one never replaces it.
162fn 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
222/// From the stored state alone, so every instance holding the same state agrees on it.
223fn 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
268/// A failed read keeps what is held, so an unreachable database serves the last known state.
269async 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}