Search code examples
rustatomicpsutil

Adopt AtomicPtr on structs without Send/Sync traits


Recently I'm trying to monitor the system metrics via psutil crate. (I know there is another library heim with async feature.)

I once used AtomicPtr to wrap the static Process struct. And the compiler complained nothing. But soon I got run-time errors, when I called the API on the Process object.

I finally figured out that the pid of the AtomicPtr<Process> changed, which led to the error, since the process with new pid did not exist.

Then I tried another way. I implemented the following ProcessWrapper, it works. And the pid did not change.

Edit: The ProcessWrapper is redundant, Process itself is with Send/Sync

Then here comes my questions:

  • Why the first method did not work? It is due to the fact that Process is without Send trait? If so, why compiler just acquiesced it?
  • Is there any potential risk in my second implementation on ProcessWrapper? If exists, how to eliminate it?
  • In the following example code, a more interesting thing is that: When you uncomment the second implementation, both of them work. But why?
use psutil::process::Process;
use lazy_static::lazy_static;
use std::process;
use std::sync::{
    atomic::{AtomicPtr, AtomicU64, Ordering}, Mutex, Arc
};

lazy_static!{
    static ref PROCESS1 : AtomicPtr<Process> = AtomicPtr::new(&mut Process::new(process::id()).unwrap());
    static ref PROCESS2: Arc<Mutex<Process>> = Arc::new(Mutex::new(
        Process::new(std::process::id()).unwrap()
    ));

}


fn main() {
    let process1 = unsafe{PROCESS1.load(Ordering::SeqCst).as_ref()}.unwrap();
    //let process2 = &PROCESS2.lock().unwrap(); 
    println!("{}", process1.pid()); // Unexpected! It is different from the real `process::id`
    //println!("{}", process2.pid()); // It performs well, and it also makes the above one valid
    println!("{}", process::id());
}



// redundant, the `Process` is with `Send/Sync` trait
use process_wrapper::*;
mod process_wrapper{
    use psutil::process::{Process, ProcessResult,MemoryInfo};
    use psutil::Percent;

    #[derive(Debug)]
    pub struct ProcessWrapper{
        pub p: Process
    }
    
    unsafe impl Send for ProcessWrapper{}
    unsafe impl Sync for ProcessWrapper{}
}



Solution

  • AtomicPtr::new(&mut Process::new(process::id()).unwrap())
    

    You create the Process object on the stack, and store a mutable pointer to that stack object into the AtomicPtr. Once PROCESS1 has finished initializing, the Process object is deallocated, and your AtomicPtr is pointing to uninitialized memory.

    psutil:process::Process is both Send and Sync. Arc<Mutex<Process>> will work fine, and is safe. Not sure why you need an AtomicPtr.