use std::{
future::Future,
mem,
sync::{Arc, Mutex, OnceLock},
thread,
};
use tracing::debug_span;
use crate::{
TestBootstrapSettings,
cluster::{WorkerInvoker, WorkerOperation},
error::BootstrapResult,
};
#[doc(hidden)]
pub type RunRootOperationHook = Arc<
dyn Fn(
&TestBootstrapSettings,
&[(String, Option<String>)],
WorkerOperation,
) -> BootstrapResult<()>
+ Send
+ Sync,
>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[doc(hidden)]
pub enum RunRootOperationHookInstallError {
#[error("run_root_operation_hook already installed")]
AlreadyInstalled,
}
static RUN_ROOT_OPERATION_HOOK: OnceLock<Mutex<Option<RunRootOperationHook>>> = OnceLock::new();
static RUN_ROOT_OPERATION_HOOK_LOGS: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
#[doc(hidden)]
#[must_use]
pub fn drain_hook_install_logs() -> Vec<String> {
let mut guard = RUN_ROOT_OPERATION_HOOK_LOGS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
mem::take(&mut *guard)
}
#[doc(hidden)]
pub fn run_root_operation_hook() -> &'static Mutex<Option<RunRootOperationHook>> {
RUN_ROOT_OPERATION_HOOK.get_or_init(|| Mutex::new(None))
}
pub struct HookGuard;
struct ThreadScope {
label: String,
span: tracing::Span,
}
fn capture_thread_scope() -> ThreadScope {
let current_thread = thread::current();
let thread_name = current_thread.name().unwrap_or("unnamed").to_owned();
let thread_id = format!("{:?}", current_thread.id());
let label = format!("{thread_name} ({thread_id})");
let span = debug_span!(
"install_run_root_operation_hook",
thread.name = %thread_name,
thread.id = %thread_id
);
ThreadScope { label, span }
}
fn log_duplicate_install(thread_label: &str) {
let message = format!("run_root_operation_hook already installed by thread {thread_label}");
record_duplicate_install(&message);
}
fn record_duplicate_install(message: &str) {
RUN_ROOT_OPERATION_HOOK_LOGS
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(message.to_owned());
#[cfg(test)]
{
log_duplicate_hook_to_stderr(message);
tracing::warn!("{message}");
}
}
#[cfg(test)]
pub(crate) fn log_duplicate_hook_to_stderr(message: &str) {
use std::io::Write as _;
let mut stderr = std::io::stderr();
if let Err(err) = writeln!(stderr, "{message}") {
tracing::warn!(?err, "failed to log duplicate hook install");
}
}
fn register_hook<F>(hook: F, thread_label: &str) -> Result<(), RunRootOperationHookInstallError>
where
F: Fn(
&TestBootstrapSettings,
&[(String, Option<String>)],
WorkerOperation,
) -> BootstrapResult<()>
+ Send
+ Sync
+ 'static,
{
let slot = run_root_operation_hook();
let mut guard = slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if guard.is_some() {
log_duplicate_install(thread_label);
return Err(RunRootOperationHookInstallError::AlreadyInstalled);
}
*guard = Some(Arc::new(hook));
Ok(())
}
pub fn install_run_root_operation_hook<F>(
hook: F,
) -> Result<HookGuard, RunRootOperationHookInstallError>
where
F: Fn(
&TestBootstrapSettings,
&[(String, Option<String>)],
WorkerOperation,
) -> BootstrapResult<()>
+ Send
+ Sync
+ 'static,
{
let scope = capture_thread_scope();
let _entered = scope.span.enter();
register_hook(hook, &scope.label)?;
Ok(HookGuard)
}
impl Drop for HookGuard {
fn drop(&mut self) {
let slot = run_root_operation_hook();
let mut guard = slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take();
}
}
#[doc(hidden)]
pub fn invoke_with_privileges<Fut>(
invoker: &WorkerInvoker<'_>,
operation: WorkerOperation,
in_process_op: Fut,
) -> BootstrapResult<()>
where
Fut: Future<Output = Result<(), postgresql_embedded::Error>> + Send,
{
invoker.invoke(operation, in_process_op)
}