Remove use of select!
This commit is contained in:
parent
4d03519631
commit
cf0a98b7a5
|
@ -0,0 +1,54 @@
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn install_handler<F>(handler: F)
|
||||||
|
where F: Fn() + 'static + Send + Sync {
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use nix::sys::signal::*;
|
||||||
|
|
||||||
|
// Mask all termination signals
|
||||||
|
// These propagate to all threads started after this point
|
||||||
|
let mut mask = SigSet::empty();
|
||||||
|
mask.add(SIGTERM);
|
||||||
|
mask.add(SIGINT);
|
||||||
|
mask.thread_set_mask().expect("unable to set signal mask");
|
||||||
|
|
||||||
|
// Spawn a thread to catch these signals
|
||||||
|
thread::spawn(move || {
|
||||||
|
let sig = mask.wait().expect("unable to sigwait");
|
||||||
|
|
||||||
|
// Invoke closure
|
||||||
|
handler();
|
||||||
|
|
||||||
|
// Restore default behavior for received signal and unmask it
|
||||||
|
unsafe {
|
||||||
|
let _ = sigaction(sig, &SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_mask = SigSet::empty();
|
||||||
|
new_mask.add(sig);
|
||||||
|
let _ = new_mask.thread_unblock();
|
||||||
|
|
||||||
|
// Re-raise, killing the process
|
||||||
|
let _ = raise(sig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On Windows, use SetConsoleCtrlHandler() to send an interrupt
|
||||||
|
/// SetConsoleCtrlHandler runs in it's own thread, so it's safe.
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn install() -> Receiver<()> {
|
||||||
|
use kernel32::SetConsoleCtrlHandler;
|
||||||
|
use winapi::{BOOL, DWORD, TRUE};
|
||||||
|
|
||||||
|
pub unsafe extern "system" fn ctrl_handler(_: DWORD) -> BOOL {
|
||||||
|
let _ = send_interrupt();
|
||||||
|
TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
let rx = create_channel();
|
||||||
|
unsafe {
|
||||||
|
SetConsoleCtrlHandler(Some(ctrl_handler), TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
rx
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender, SendError};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref INTERRUPT_TX: Mutex<Option<Sender<()>>> = Mutex::new(None);
|
|
||||||
static ref INTERRUPT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On Unix platforms, mask reception of SIGINT/SIGTERM, spawn a thread,
|
|
||||||
/// and sigwait on those signals to safely relay them.
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn install() -> Receiver<()> {
|
|
||||||
use std::thread;
|
|
||||||
use nix::sys::signal::{SigSet, SIGTERM, SIGINT};
|
|
||||||
|
|
||||||
let mut mask = SigSet::empty();
|
|
||||||
mask.add(SIGTERM);
|
|
||||||
mask.add(SIGINT);
|
|
||||||
mask.thread_set_mask().expect("unable to set signal mask");
|
|
||||||
|
|
||||||
let rx = create_channel();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
let _ = mask.wait().expect("unable to sigwait");
|
|
||||||
|
|
||||||
let result = send_interrupt();
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On Windows, use SetConsoleCtrlHandler() to send an interrupt
|
|
||||||
/// SetConsoleCtrlHandler runs in it's own thread, so it's safe.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn install() -> Receiver<()> {
|
|
||||||
use kernel32::SetConsoleCtrlHandler;
|
|
||||||
use winapi::{BOOL, DWORD, TRUE};
|
|
||||||
|
|
||||||
pub unsafe extern "system" fn ctrl_handler(_: DWORD) -> BOOL {
|
|
||||||
let _ = send_interrupt();
|
|
||||||
TRUE
|
|
||||||
}
|
|
||||||
|
|
||||||
let rx = create_channel();
|
|
||||||
unsafe {
|
|
||||||
SetConsoleCtrlHandler(Some(ctrl_handler), TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interrupt_requested() -> bool {
|
|
||||||
INTERRUPT_REQUESTED.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_channel() -> Receiver<()> {
|
|
||||||
let mut guard = INTERRUPT_TX.lock().unwrap();
|
|
||||||
if (*guard).is_some() {
|
|
||||||
panic!("interrupt_handler::install() already called!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
(*guard) = Some(tx);
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_interrupt() -> Result<(), SendError<()>> {
|
|
||||||
INTERRUPT_REQUESTED.store(true, Ordering::Relaxed);
|
|
||||||
|
|
||||||
if let Some(ref mut tx) = *INTERRUPT_TX.lock().unwrap() {
|
|
||||||
tx.send(())
|
|
||||||
} else {
|
|
||||||
Err(SendError(()))
|
|
||||||
}
|
|
||||||
}
|
|
113
src/main.rs
113
src/main.rs
|
@ -1,4 +1,3 @@
|
||||||
#![feature(mpsc_select)]
|
|
||||||
#![feature(process_exec)]
|
#![feature(process_exec)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -24,19 +23,20 @@ extern crate mktemp;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod gitignore;
|
mod gitignore;
|
||||||
mod interrupt_handler;
|
mod interrupt;
|
||||||
mod notification_filter;
|
mod notification_filter;
|
||||||
mod process;
|
mod process;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
use std::sync::mpsc::{channel, Receiver};
|
use std::sync::mpsc::{channel, Receiver};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use notification_filter::NotificationFilter;
|
use notification_filter::NotificationFilter;
|
||||||
use process::{Process, ProcessReaper};
|
use process::{Process};
|
||||||
use watcher::{Event, Watcher};
|
use watcher::{Event, Watcher};
|
||||||
|
|
||||||
fn find_gitignore(path: &Path) -> Option<PathBuf> {
|
fn find_gitignore(path: &Path) -> Option<PathBuf> {
|
||||||
|
@ -79,7 +79,19 @@ fn init_logger(debug: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let interrupt_rx = interrupt_handler::install();
|
let child_process: Arc<RwLock<Option<Process>>> = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
|
let weak_child = Arc::downgrade(&child_process);
|
||||||
|
interrupt::install_handler(move || {
|
||||||
|
if let Some(lock) = weak_child.upgrade() {
|
||||||
|
let strong = lock.read().unwrap();
|
||||||
|
if let Some(ref child) = *strong {
|
||||||
|
child.kill();
|
||||||
|
child.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let args = cli::get_args();
|
let args = cli::get_args();
|
||||||
|
|
||||||
init_logger(args.debug);
|
init_logger(args.debug);
|
||||||
|
@ -119,89 +131,74 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd = args.cmd;
|
// Start child process initially, if necessary
|
||||||
|
if args.run_initially {
|
||||||
let (child_finish_tx, child_finish_rx) = channel();
|
if args.clear_screen {
|
||||||
let reaper = ProcessReaper::new(child_finish_tx);
|
|
||||||
|
|
||||||
let mut child_process = if args.run_initially {
|
|
||||||
if args.clear_screen {
|
|
||||||
cli::clear_screen();
|
cli::clear_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
Process::new(&cmd, vec![]).ok()
|
let mut guard = child_process.write().unwrap();
|
||||||
} else {
|
*guard = Process::new(&args.cmd, vec![]).ok();
|
||||||
None
|
}
|
||||||
};
|
|
||||||
|
|
||||||
while !interrupt_handler::interrupt_requested() {
|
loop {
|
||||||
if let Some(paths) = wait(&rx, &interrupt_rx, &filter) {
|
let paths = wait(&rx, &filter);
|
||||||
|
if let Some(path) = paths.get(0) {
|
||||||
|
debug!("Path updated: {:?}", path);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path) = paths.get(0) {
|
//. Wait for current child process to exit
|
||||||
debug!("Path updated: {:?}", path);
|
{
|
||||||
}
|
let guard = child_process.read().unwrap();
|
||||||
|
|
||||||
if let Some(mut child) = child_process {
|
if let Some(ref child) = *guard {
|
||||||
if args.restart {
|
if args.restart {
|
||||||
debug!("Killing child process");
|
debug!("Killing child process");
|
||||||
child.kill();
|
child.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Waiting for process to exit...");
|
debug!("Waiting for process to exit...");
|
||||||
reaper.wait_process(child);
|
child.wait();
|
||||||
select! {
|
|
||||||
_ = child_finish_rx.recv() => {},
|
|
||||||
_ = interrupt_rx.recv() => break
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if args.clear_screen {
|
// Launch child process
|
||||||
cli::clear_screen();
|
if args.clear_screen {
|
||||||
}
|
cli::clear_screen();
|
||||||
|
}
|
||||||
|
|
||||||
child_process = Process::new(&cmd, paths).ok();
|
{
|
||||||
|
let mut lock = child_process.write().unwrap();
|
||||||
|
*lock = Process::new(&args.cmd, paths).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait(rx: &Receiver<Event>,
|
fn wait(rx: &Receiver<Event>, filter: &NotificationFilter) -> Vec<PathBuf> {
|
||||||
interrupt_rx: &Receiver<()>,
|
|
||||||
filter: &NotificationFilter)
|
|
||||||
-> Option<Vec<PathBuf>> {
|
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
let mut cache = HashMap::new();
|
let mut cache = HashMap::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
let e = rx.recv().expect("error when reading event");
|
||||||
_ = interrupt_rx.recv() => { return None; },
|
|
||||||
ev = rx.recv() => {
|
|
||||||
let e = ev.expect("error when reading event");
|
|
||||||
|
|
||||||
if let Some(ref path) = e.path {
|
if let Some(ref path) = e.path {
|
||||||
// Ignore cache for the initial file. Otherwise, in
|
// Ignore cache for the initial file. Otherwise, in
|
||||||
// debug mode it's hard to track what's going on
|
// debug mode it's hard to track what's going on
|
||||||
let excluded = filter.is_excluded(path);
|
let excluded = filter.is_excluded(path);
|
||||||
if !cache.contains_key(path) {
|
if !cache.contains_key(path) {
|
||||||
cache.insert(path.to_owned(), excluded);
|
cache.insert(path.to_owned(), excluded);
|
||||||
}
|
|
||||||
|
|
||||||
if !excluded {
|
|
||||||
paths.push(path.to_owned());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
if !excluded {
|
||||||
|
paths.push(path.to_owned());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for filesystem activity to cool off
|
// Wait for filesystem activity to cool off
|
||||||
// Unfortunately, we can't use select! with recv_timeout :(
|
|
||||||
let timeout = Duration::from_millis(500);
|
let timeout = Duration::from_millis(500);
|
||||||
while let Ok(e) = rx.recv_timeout(timeout) {
|
while let Ok(e) = rx.recv_timeout(timeout) {
|
||||||
if interrupt_handler::interrupt_requested() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref path) = e.path {
|
if let Some(ref path) = e.path {
|
||||||
if cache.contains_key(path) {
|
if cache.contains_key(path) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -218,5 +215,5 @@ fn wait(rx: &Receiver<Event>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(paths)
|
paths
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
pub use self::imp::*;
|
pub use self::imp::*;
|
||||||
|
|
||||||
|
@ -12,7 +10,6 @@ mod imp {
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pid: i32,
|
pid: i32,
|
||||||
killed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
|
@ -37,18 +34,13 @@ mod imp {
|
||||||
.and_then(|p| {
|
.and_then(|p| {
|
||||||
Ok(Process {
|
Ok(Process {
|
||||||
pid: p.id() as i32,
|
pid: p.id() as i32,
|
||||||
killed: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(&mut self) {
|
pub fn kill(&self) {
|
||||||
use libc;
|
use libc;
|
||||||
|
|
||||||
if self.killed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn killpg(pgrp: libc::pid_t, sig: libc::c_int) -> libc::c_int;
|
fn killpg(pgrp: libc::pid_t, sig: libc::c_int) -> libc::c_int;
|
||||||
}
|
}
|
||||||
|
@ -56,22 +48,14 @@ mod imp {
|
||||||
unsafe {
|
unsafe {
|
||||||
killpg(self.pid, libc::SIGTERM);
|
killpg(self.pid, libc::SIGTERM);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.killed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(&mut self) {
|
pub fn wait(&self) {
|
||||||
use nix::sys::wait::waitpid;
|
use nix::sys::wait::waitpid;
|
||||||
|
|
||||||
let _ = waitpid(-self.pid, None);
|
let _ = waitpid(-self.pid, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Process {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.kill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
|
@ -86,7 +70,6 @@ mod imp {
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
job: HANDLE,
|
job: HANDLE,
|
||||||
killed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
|
@ -134,24 +117,17 @@ mod imp {
|
||||||
|
|
||||||
Ok(Process {
|
Ok(Process {
|
||||||
job: job,
|
job: job,
|
||||||
killed: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(&mut self) {
|
pub fn kill(&self) {
|
||||||
if self.killed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = TerminateJobObject(self.job, 1);
|
let _ = TerminateJobObject(self.job, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.killed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(&mut self) {
|
pub fn wait(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = WaitForSingleObject(self.job, INFINITE);
|
let _ = WaitForSingleObject(self.job, INFINITE);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +135,7 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Process {
|
impl Drop for Process {
|
||||||
fn drop(&mut self) {
|
fn drop(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = CloseHandle(self.job);
|
let _ = CloseHandle(self.job);
|
||||||
}
|
}
|
||||||
|
@ -167,36 +143,7 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Process {}
|
unsafe impl Send for Process {}
|
||||||
}
|
unsafe impl Sync for Process {}
|
||||||
|
|
||||||
/// Watches for child process death, notifying callers via a channel.
|
|
||||||
///
|
|
||||||
/// On Windows, we don't have SIGCHLD, and even if we did, we'd still need
|
|
||||||
/// to relay that over a channel.
|
|
||||||
pub struct ProcessReaper {
|
|
||||||
processes_tx: Sender<Process>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessReaper {
|
|
||||||
pub fn new(tx: Sender<()>) -> ProcessReaper {
|
|
||||||
let (processes_tx, processes_rx): (Sender<Process>, Receiver<Process>) = channel();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
while let Ok(mut process) = processes_rx.recv() {
|
|
||||||
process.wait();
|
|
||||||
|
|
||||||
let _ = tx.send(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ProcessReaper { processes_tx: processes_tx }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_process(&self, process: imp::Process) {
|
|
||||||
let _ = self.processes_tx.send(process);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_single_updated_path(paths: &[PathBuf]) -> Option<&str> {
|
fn get_single_updated_path(paths: &[PathBuf]) -> Option<&str> {
|
||||||
|
|
Loading…
Reference in New Issue