remote_settings/service.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::{
collections::HashSet,
sync::{Arc, Weak},
};
use camino::Utf8PathBuf;
use parking_lot::Mutex;
use url::Url;
use crate::{
storage::Storage, RemoteSettingsClient, RemoteSettingsConfig2, RemoteSettingsContext,
RemoteSettingsServer, Result,
};
/// Internal Remote settings service API
pub struct RemoteSettingsService {
inner: Mutex<RemoteSettingsServiceInner>,
}
struct RemoteSettingsServiceInner {
storage_dir: Utf8PathBuf,
base_url: Url,
bucket_name: String,
/// Weakrefs for all clients that we've created. Note: this stores the
/// top-level/public `RemoteSettingsClient` structs rather than `client::RemoteSettingsClient`.
/// The reason for this is that we return Arcs to the public struct to the foreign code, so we
/// need to use the same type for our weakrefs. The alternative would be to create 2 Arcs for
/// each client, which is wasteful.
clients: Vec<Weak<RemoteSettingsClient>>,
}
impl RemoteSettingsService {
/// Construct a [RemoteSettingsService]
///
/// This is typically done early in the application-startup process
pub fn new(storage_dir: String, config: RemoteSettingsConfig2) -> Result<Self> {
let storage_dir = storage_dir.into();
let base_url = config
.server
.unwrap_or(RemoteSettingsServer::Prod)
.get_url()?;
let bucket_name = config.bucket_name.unwrap_or_else(|| String::from("main"));
Ok(Self {
inner: Mutex::new(RemoteSettingsServiceInner {
storage_dir,
base_url,
bucket_name,
clients: vec![],
}),
})
}
/// Create a new Remote Settings client
#[cfg(feature = "jexl")]
pub fn make_client(
&self,
collection_name: String,
context: Option<RemoteSettingsContext>,
) -> Result<Arc<RemoteSettingsClient>> {
let mut inner = self.inner.lock();
let storage = Storage::new(inner.storage_dir.join(format!("{collection_name}.sql")))?;
let client = Arc::new(RemoteSettingsClient::new(
inner.base_url.clone(),
inner.bucket_name.clone(),
collection_name.clone(),
context,
storage,
)?);
inner.clients.push(Arc::downgrade(&client));
Ok(client)
}
#[cfg(not(feature = "jexl"))]
pub fn make_client(
&self,
collection_name: String,
#[allow(unused_variables)] context: Option<RemoteSettingsContext>,
) -> Result<Arc<RemoteSettingsClient>> {
let mut inner = self.inner.lock();
let storage = Storage::new(inner.storage_dir.join(format!("{collection_name}.sql")))?;
let client = Arc::new(RemoteSettingsClient::new(
inner.base_url.clone(),
inner.bucket_name.clone(),
collection_name.clone(),
storage,
)?);
inner.clients.push(Arc::downgrade(&client));
Ok(client)
}
/// Sync collections for all active clients
pub fn sync(&self) -> Result<Vec<String>> {
// Make sure we only sync each collection once, even if there are multiple clients
let mut synced_collections = HashSet::new();
// TODO: poll the server using `/buckets/monitor/collections/changes/changeset` to fetch
// the current timestamp for all collections. That way we can avoid fetching collections
// we know haven't changed and also pass the `?_expected{ts}` param to the server.
for client in self.inner.lock().active_clients() {
if synced_collections.insert(client.collection_name()) {
client.internal.sync()?;
}
}
Ok(synced_collections.into_iter().collect())
}
/// Update the remote settings config
///
/// This will cause all current and future clients to use new config and will delete any stored
/// records causing the clients to return new results from the new config.
pub fn update_config(&self, config: RemoteSettingsConfig2) -> Result<()> {
let base_url = config
.server
.unwrap_or(RemoteSettingsServer::Prod)
.get_url()?;
let bucket_name = config.bucket_name.unwrap_or_else(|| String::from("main"));
let mut inner = self.inner.lock();
for client in inner.active_clients() {
client
.internal
.update_config(base_url.clone(), bucket_name.clone())?;
}
inner.base_url = base_url;
inner.bucket_name = bucket_name;
Ok(())
}
}
impl RemoteSettingsServiceInner {
// Find live clients in self.clients
//
// Also, drop dead weakrefs from the vec
fn active_clients(&mut self) -> Vec<Arc<RemoteSettingsClient>> {
let mut active_clients = vec![];
self.clients.retain(|weak| {
if let Some(client) = weak.upgrade() {
active_clients.push(client);
true
} else {
false
}
});
active_clients
}
}