use std::fs;
use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::sync::Arc;
use log::warn;
use nix::poll::{poll, PollFd, PollFlags};
use super::defs::*;
use crate::support::error::Error;
use crate::support::file_ops::IgnoreKinds;
static SOCKET_SERNO: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
pub struct IdleListener {
sock: UnixDatagram,
recv_self_notify: UnixDatagram,
send_self_notify: Arc<UnixDatagram>,
sock_path: PathBuf,
symlink_path: PathBuf,
}
impl IdleListener {
pub fn notifier(&self) -> IdleNotifier {
IdleNotifier {
sock: Arc::clone(&self.send_self_notify),
}
}
pub fn idle(self) -> io::Result<()> {
self.idle_impl(-1)
}
fn idle_impl(self, timeout: nix::libc::c_int) -> io::Result<()> {
let mut pollfd = [
PollFd::new(
self.sock.as_raw_fd(),
PollFlags::POLLIN | PollFlags::POLLERR,
),
PollFd::new(
self.recv_self_notify.as_raw_fd(),
PollFlags::POLLIN | PollFlags::POLLERR,
),
];
loop {
match poll(&mut pollfd, timeout) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"IDLE timed out",
))
}
Ok(_) => {
return Ok(());
}
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => continue,
Err(e) => {
return Err(io::Error::from_raw_os_error(
e.as_errno().unwrap() as i32,
));
}
}
}
}
#[cfg(test)]
fn idle_instant(self) -> io::Result<()> {
self.idle_impl(0)
}
}
impl Drop for IdleListener {
fn drop(&mut self) {
let _ = fs::remove_file(&self.symlink_path);
let _ = fs::remove_file(&self.sock_path);
}
}
#[derive(Debug, Clone)]
pub struct IdleNotifier {
sock: Arc<UnixDatagram>,
}
impl IdleNotifier {
pub fn notify(self) -> io::Result<()> {
self.sock.send(&[0]).map(|_| ())
}
}
impl StatelessMailbox {
pub fn prepare_idle(&self) -> Result<IdleListener, Error> {
fs::DirBuilder::new()
.mode(0o770)
.create(&self.path.socks_path)
.ignore_already_exists()?;
let serno = SOCKET_SERNO.fetch_add(1, SeqCst);
let sock_name = format!("{}.{}", nix::unistd::getpid(), serno);
let symlink_path = self.path.socks_path.join(&sock_name);
let sock_path = self.common_paths.tmp.join(&sock_name);
let _ = fs::remove_file(&sock_path);
let _ = fs::remove_file(&symlink_path);
let (self_recv, self_send) = UnixDatagram::pair()?;
let listener = IdleListener {
sock: UnixDatagram::bind(&sock_path)?,
recv_self_notify: self_recv,
send_self_notify: Arc::new(self_send),
sock_path,
symlink_path,
};
std::os::unix::fs::symlink(&sock_name, &listener.symlink_path)?;
Ok(listener)
}
pub(super) fn notify_all(&self) -> Result<(), Error> {
let sock = UnixDatagram::unbound()?;
let dirit = match fs::read_dir(&self.path.socks_path) {
Ok(d) => d,
Err(e) if io::ErrorKind::NotFound == e.kind() => return Ok(()),
Err(e) => return Err(e.into()),
};
for entry in dirit {
let entry = entry?;
let path = entry.path();
let destination = self.common_paths.tmp.join(entry.file_name());
let _ = sock.send_to(&[0], &destination);
let _ = fs::remove_file(&destination);
let _ = fs::remove_file(&path);
}
Ok(())
}
pub(super) fn notify_all_best_effort(&self) {
if let Err(e) = self.notify_all() {
warn!("{} Failed to send notifications: {}", self.log_prefix, e);
}
}
}
#[cfg(test)]
mod test {
use std::io;
use super::super::test_prelude::*;
use crate::account::model::*;
#[test]
fn notify_nobody() {
let setup = set_up();
setup.stateless.notify_all().unwrap();
}
#[test]
fn listener_would_block_without_notification() {
let setup = set_up();
match setup.stateless.prepare_idle().unwrap().idle_instant() {
Err(e) if io::ErrorKind::TimedOut == e.kind() => (),
Err(e) => panic!("Unexpected error: {}", e),
Ok(_) => panic!("Didn't block"),
}
}
#[test]
fn notify_self() {
let setup = set_up();
let listener = setup.stateless.prepare_idle().unwrap();
listener.notifier().notify().unwrap();
listener.idle().unwrap();
}
#[test]
fn notify_on_message_insert() {
let setup = set_up();
let listener = setup.stateless.prepare_idle().unwrap();
simple_append(&setup.stateless);
listener.idle_instant().unwrap();
}
#[test]
fn notify_on_blind_flags_set() {
let setup = set_up();
let uid = simple_append(&setup.stateless);
let listener = setup.stateless.prepare_idle().unwrap();
setup
.stateless
.set_flags_blind(vec![(uid, vec![(true, Flag::Flagged)])])
.unwrap();
listener.idle_instant().unwrap();
}
#[test]
fn notify_on_multi_message_insert() {
let setup = set_up();
let uid1 = simple_append(&setup.stateless);
let uid2 = simple_append(&setup.stateless);
let (mut mb1, _) = setup.stateless.clone().select().unwrap();
let listener = setup.stateless.prepare_idle().unwrap();
mb1.copy(
&CopyRequest {
ids: SeqRange::range(uid1, uid2),
},
&setup.stateless,
)
.unwrap();
listener.idle_instant().unwrap();
}
#[test]
fn notify_on_change_tx() {
let setup = set_up();
let uid = simple_append(&setup.stateless);
let (mut mb1, _) = setup.stateless.clone().select().unwrap();
let listener = setup.stateless.prepare_idle().unwrap();
mb1.vanquish(&SeqRange::just(uid)).unwrap();
listener.idle_instant().unwrap();
}
}