How to share epoll fd between threads

Hallo,

I am trying to implement an epoll loop like I would do it in C/C++ but I get problems in sharing the epollfd.
epoll::wait should run in an own thread so that I can add,del,mod filedescriptors from outside and close the epollfd from outside.

I want to use epoll::wait with an infinite timeout. So this call is blocking. It would be released if the epollfd is closed.

If I encapsulate the epollfd into an Arc::Mutex it would block on epoll::wait and I got a deadlock on add,del,mod filedescriptors and on clonsing the epollfd from outside.

How could I solve this in Rust?

Thanks

use std::{thread::JoinHandle, sync::{atomic::{AtomicBool, Ordering, AtomicI32}, Arc}};

struct Io {
    epfd: i32,
    hdl: Option<JoinHandle<()>>,
    is_running: Arc<AtomicBool>,
}

impl Drop for Io {
    fn drop(&mut self) {
        println!("1");
        self.is_running.store(false, Ordering::Relaxed);   
        println!("2"); 
        //I think it is not working because this is not the epollfd that is used in epoll::wait    
        epoll::close(self.epfd).unwrap();
        println!("3");
        if let Some(hdl) = self.hdl.take() {
            hdl.join().unwrap();
        }
        println!("drop");
    }
}

impl Io {
    pub fn new() -> std::io::Result<Self> {
        let epfd = epoll::create(false)?;

        Ok(Self {
            epfd,
            hdl: None,
            is_running: Arc::new(AtomicBool::new(false)),
        })
    }

    //Example
    pub fn add(&self, fd: i32) {
        epoll::ctl(
            self.epfd,  
            epoll::ControlOptions::EPOLL_CTL_ADD, 
            fd, 
            epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLRDHUP | epoll::Events::EPOLLHUP | epoll::Events::EPOLLERR,
            0)
        ).unwrap();
    }

    fn start(&mut self) {
        self.is_running.store(true, Ordering::Relaxed);

        //How to share epfd properly? 
        let epfd = self.epfd;   

        let is_running = Arc::clone(&self.is_running);
        self.hdl = Some(std::thread::spawn(move || {
            while is_running.load(Ordering::Relaxed) {
                println!("before epoll");
                let mut events = [epoll::Event::new(epoll::Events::empty(), 0); 10];
                let event_count = epoll::wait(epfd, -1, &mut events).unwrap();
                println!("after epoll");
            }            
        }));
    }
}

Just make all the methods take &self. Then there is no problem sharing it.

I could do that, but then I have to discard the Joinhandle and create a detached thread. It is a long running thread and should run as long as the Io-Object lives. So will it be cleaned automatically by Rust/OS after drop of Io was called?

You can still use &mut self in your destructor.