add config reloading indirector
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
1351d07735
commit
7c6b8b132a
2 changed files with 130 additions and 1 deletions
128
src/core/config/manager.rs
Normal file
128
src/core/config/manager.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
ops::Deref,
|
||||||
|
ptr,
|
||||||
|
ptr::null_mut,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicPtr, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Config;
|
||||||
|
use crate::{implement, Result};
|
||||||
|
|
||||||
|
/// The configuration manager is an indirection to reload the configuration for
|
||||||
|
/// the server while it is running. In order to not burden or clutter the many
|
||||||
|
/// callsites which query for configuration items, this object implements Deref
|
||||||
|
/// for the actively loaded configuration.
|
||||||
|
pub struct Manager {
|
||||||
|
active: AtomicPtr<Config>,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static INDEX: Cell<usize> = 0.into();
|
||||||
|
static HANDLE: RefCell<Handles> = const {
|
||||||
|
RefCell::new([const { None }; HISTORY])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handle = Option<Arc<Config>>;
|
||||||
|
type Handles = [Handle; HISTORY];
|
||||||
|
|
||||||
|
const HISTORY: usize = 8;
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub(crate) fn new(config: Config) -> Self {
|
||||||
|
let config = Arc::new(config);
|
||||||
|
Self {
|
||||||
|
active: AtomicPtr::new(Arc::into_raw(config).cast_mut()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Manager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let config = self.active.swap(null_mut(), Ordering::AcqRel);
|
||||||
|
|
||||||
|
// SAFETY: The active pointer was set using an Arc::into_raw(). We're obliged to
|
||||||
|
// reconstitute that into Arc otherwise it will leak.
|
||||||
|
unsafe { Arc::from_raw(config) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Manager {
|
||||||
|
type Target = Arc<Config>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { HANDLE.with_borrow_mut(|handle| self.load(handle)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the active configuration, returning prior configuration.
|
||||||
|
#[implement(Manager)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn update(&self, config: Config) -> Result<Arc<Config>> {
|
||||||
|
let config = Arc::new(config);
|
||||||
|
let new = Arc::into_raw(config);
|
||||||
|
let old = self.active.swap(new.cast_mut(), Ordering::AcqRel);
|
||||||
|
|
||||||
|
// SAFETY: The old active pointer was set using an Arc::into_raw(). We're
|
||||||
|
// obliged to reconstitute that into Arc otherwise it will leak.
|
||||||
|
Ok(unsafe { Arc::from_raw(old) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[implement(Manager)]
|
||||||
|
fn load(&self, handle: &mut [Option<Arc<Config>>]) -> &'static Arc<Config> {
|
||||||
|
let config = self.active.load(Ordering::Acquire);
|
||||||
|
|
||||||
|
// Branch taken after config reload or first access by this thread.
|
||||||
|
if handle[INDEX.get()]
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|handle| !ptr::eq(config, Arc::as_ptr(handle)))
|
||||||
|
{
|
||||||
|
INDEX.set(INDEX.get().wrapping_add(1).wrapping_rem(HISTORY));
|
||||||
|
return load_miss(handle, INDEX.get(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config: &Arc<Config> = handle[INDEX.get()]
|
||||||
|
.as_ref()
|
||||||
|
.expect("handle was already cached for this thread");
|
||||||
|
|
||||||
|
// SAFETY: The caller should not hold multiple references at a time directly
|
||||||
|
// into Config, as a subsequent reference might invalidate the thread's cache
|
||||||
|
// causing another reference to dangle.
|
||||||
|
//
|
||||||
|
// This is a highly unusual pattern as most config values are copied by value or
|
||||||
|
// used immediately without running overlap with another value. Even if it does
|
||||||
|
// actually occur somewhere, the window of danger is limited to the config being
|
||||||
|
// reloaded while the reference is held and another access is made by the same
|
||||||
|
// thread into a different config value. This is mitigated by creating a buffer
|
||||||
|
// of old configs rather than discarding at the earliest opportunity; the odds
|
||||||
|
// of this scenario are thus astronomical.
|
||||||
|
unsafe { std::mem::transmute(config) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
name = "miss",
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
fields(%index, ?config)
|
||||||
|
)]
|
||||||
|
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||||
|
fn load_miss(
|
||||||
|
handle: &mut [Option<Arc<Config>>],
|
||||||
|
index: usize,
|
||||||
|
config: *const Config,
|
||||||
|
) -> &'static Arc<Config> {
|
||||||
|
// SAFETY: The active pointer was set prior and always remains valid. We're
|
||||||
|
// reconstituting the Arc here but as a new reference, so the count is
|
||||||
|
// incremented. This instance will be cached in the thread-local.
|
||||||
|
let config = unsafe {
|
||||||
|
Arc::increment_strong_count(config);
|
||||||
|
Arc::from_raw(config)
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: See the note on the transmute above. The caller should not hold more
|
||||||
|
// than one reference at a time directly into Config, as the second access
|
||||||
|
// might invalidate the thread's cache, dangling the reference to the first.
|
||||||
|
unsafe { std::mem::transmute(handle[index].insert(config)) }
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod check;
|
pub mod check;
|
||||||
|
pub mod manager;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -22,8 +23,8 @@ use ruma::{
|
||||||
use serde::{de::IgnoredAny, Deserialize};
|
use serde::{de::IgnoredAny, Deserialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub use self::check::check;
|
|
||||||
use self::proxy::ProxyConfig;
|
use self::proxy::ProxyConfig;
|
||||||
|
pub use self::{check::check, manager::Manager};
|
||||||
use crate::{err, error::Error, utils::sys, Result};
|
use crate::{err, error::Error, utils::sys, Result};
|
||||||
|
|
||||||
/// All the config options for conduwuit.
|
/// All the config options for conduwuit.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue