//! Allocation-free handoff from arbitrary final-file context to a sleepable
//! perf-event release worker.

use alloc::{boxed::Box, sync::Arc};
use core::{
    ptr,
    sync::atomic::{AtomicPtr, Ordering},
};

use crate::{
    libs::wait_queue::WaitQueue,
    process::kthread::{KernelThreadClosure, KernelThreadMechanism},
};

use super::PerfEventCore;

static HEAD: AtomicPtr<PerfReleaseNode> = AtomicPtr::new(ptr::null_mut());
static WAIT: WaitQueue = WaitQueue::default();

/// The node is a separate stable allocation. Ownership moves only in the
/// direction inode -> intrusive queue -> worker; `PerfEventCore` never owns a
/// node, so there is no self-referential Arc cycle.
#[derive(Debug)]
pub(super) struct PerfReleaseNode {
    next: AtomicPtr<PerfReleaseNode>,
    core: Arc<PerfEventCore>,
}

impl PerfReleaseNode {
    pub(super) fn new(core: Arc<PerfEventCore>) -> Self {
        Self {
            next: AtomicPtr::new(ptr::null_mut()),
            core,
        }
    }
}

pub(super) fn enqueue(node: Box<PerfReleaseNode>) {
    let node = Box::into_raw(node);
    let mut head = HEAD.load(Ordering::Acquire);
    loop {
        unsafe { (*node).next.store(head, Ordering::Relaxed) };
        match HEAD.compare_exchange_weak(head, node, Ordering::Release, Ordering::Acquire) {
            Ok(_) => break,
            Err(actual) => head = actual,
        }
    }
    WAIT.wakeup(None);
}

pub(crate) fn init() {
    let kt_closure = KernelThreadClosure::EmptyClosure((Box::new(worker_loop), ()));
    KernelThreadMechanism::create_and_run(kt_closure, "perf_release".into())
        .expect("failed to create perf release worker");
}

fn worker_loop() -> i32 {
    loop {
        WAIT.wait_until(|| {
            if HEAD.load(Ordering::Acquire).is_null() {
                None
            } else {
                Some(())
            }
        });

        let mut list = HEAD.swap(ptr::null_mut(), Ordering::Acquire);
        let mut reversed = ptr::null_mut();
        while !list.is_null() {
            let next = unsafe { (*list).next.load(Ordering::Relaxed) };
            unsafe { (*list).next.store(reversed, Ordering::Relaxed) };
            reversed = list;
            list = next;
        }

        while !reversed.is_null() {
            let node = unsafe { Box::from_raw(reversed) };
            reversed = node.next.load(Ordering::Relaxed);
            node.next.store(ptr::null_mut(), Ordering::Relaxed);
            if let Err(_error) = super::PerfEventOps::release(node.core.event.as_ref()) {
                // A published event must be releasable after the boot-time CPU
                // rendezvous probe succeeds. Preserve callbacks before the
                // no-unwind fatal path: dropping them while the branch remains
                // enabled would be memory unsafe.
                core::mem::forget(node);
                crate::text_patch::fatal_text_invariant();
            }
            drop(node);
        }
    }
}
