use crate::errors::{Error, ErrorEnum};
use crate::generation::MTGeneration;
use crate::generation::defer_gc::maybe_defer_sync_task;
use crate::generation::stats::GcStats;
use std::fmt;
use std::marker::PhantomData;
use std::sync::mpsc;
use std::sync::{RwLock, Weak};
use std::thread;
use std::thread::JoinHandle;
type TaskPtr = Box<dyn Send + Sync + Fn(GcTask)>;
static GC_TASK_FN: RwLock<Option<TaskPtr>> = RwLock::new(None);
#[derive(Debug)]
pub struct GcTask {
generation: Option<Weak<MTGeneration>>,
}
pub struct GcTaskCallback<'a> {
lifetime: PhantomData<&'a ()>,
}
#[derive(Debug)]
pub struct GcThread {
handle: Option<GcTaskCallback<'static>>,
thread: Option<JoinHandle<()>>,
}
impl Drop for GcTask {
#[inline]
fn drop(&mut self) {
if let Some(generation) = self.generation.take().as_ref().and_then(Weak::upgrade) {
generation.run_gc();
}
}
}
impl GcTask {
pub(super) const fn new(generation: Weak<MTGeneration>) -> Self {
GcTask {
generation: Some(generation),
}
}
#[inline]
pub fn run(mut self) -> Option<GcStats> {
self.generation
.take()
.as_ref()
.and_then(Weak::upgrade)
.map(MTGeneration::run_gc)
}
pub(super) fn post(self) -> Option<Self> {
maybe_defer_sync_task(self)
.err()
.and_then(Self::post_no_defer)
}
pub(super) fn post_no_defer(self) -> Option<Self> {
match GC_TASK_FN.read() {
Ok(gc_task_fn) => {
if let Some(gc_task_fn) = &*gc_task_fn {
gc_task_fn(self);
None
} else {
Some(self)
}
}
Err(_) => Some(self),
}
}
#[allow(
clippy::missing_inline_in_public_items,
reason = "This is rarely called."
)]
pub fn install_callback<'a>(
callback: impl Fn(GcTask) + Send + Sync + 'a,
) -> Result<GcTaskCallback<'a>, Error> {
let gc_task_fn = &mut *GC_TASK_FN
.write()
.map_err(|_| Error::new(ErrorEnum::InternalLockingError))?;
if gc_task_fn.is_some() {
Err(Error::new(ErrorEnum::CallbackAlreadyInstalled))
} else {
let callback = Box::new(callback);
let callback: Box<dyn Fn(GcTask) + Send + Sync + 'a> = callback;
let callback = unsafe {
std::mem::transmute::<
Box<dyn Fn(GcTask) + Send + Sync + 'a>,
Box<dyn Fn(GcTask) + Send + Sync + 'static>,
>(callback)
};
*gc_task_fn = Some(callback);
Ok(GcTaskCallback {
lifetime: PhantomData,
})
}
}
#[inline(never)]
pub fn gc_thread() -> Result<GcThread, Error> {
let (tx, rx) = mpsc::channel();
let handle = Self::install_callback(move |task| {
let _ = tx.send(task);
})?;
let thread = thread::Builder::new()
.name("gc".into())
.spawn(move || {
rx.iter().for_each(|task| {
task.run();
});
})
.map_err(|io_err| Error::new(ErrorEnum::ThreadSpawnFailed(io_err.into())))?;
Ok(GcThread {
handle: Some(handle),
thread: Some(thread),
})
}
}
impl Drop for GcTaskCallback<'_> {
#[allow(
clippy::missing_inline_in_public_items,
reason = "This is rarely called."
)]
fn drop(&mut self) {
GC_TASK_FN.write().unwrap().take();
}
}
impl fmt::Debug for GcTaskCallback<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str("GcTaskCallback")
}
}
impl Drop for GcThread {
#[inline]
fn drop(&mut self) {
self.handle = None; if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
impl GcThread {
#[inline]
pub fn drop_no_wait(mut self) -> JoinHandle<()> {
self.thread.take().unwrap()
}
}