Skip to content

Commit 6c9a81c

Browse files
committed
dd: simplify signal handling by removing Alarm timer thread
1 parent ba1afb0 commit 6c9a81c

File tree

5 files changed

+49
-146
lines changed

5 files changed

+49
-146
lines changed

Cargo.lock

Lines changed: 2 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/dd/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ thiserror = { workspace = true }
3232
fluent = { workspace = true }
3333

3434
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
35-
nix = { workspace = true, features = ["fs"] }
36-
signal-hook = { workspace = true }
35+
nix = { workspace = true, features = ["fs", "signal"] }
3736

3837
[[bin]]
3938
name = "dd"

src/uu/dd/src/dd.rs

Lines changed: 18 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use nix::fcntl::OFlag;
2323
use parseargs::Parser;
2424
use progress::ProgUpdateType;
2525
use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater};
26+
#[cfg(target_os = "linux")]
27+
use progress::{check_and_reset_sigusr1, install_sigusr1_handler};
2628
use uucore::io::OwnedFileDescriptorOrHandle;
2729
use uucore::translate;
2830

@@ -45,8 +47,7 @@ use std::os::unix::{
4547
#[cfg(windows)]
4648
use std::os::windows::{fs::MetadataExt, io::AsHandle};
4749
use std::path::Path;
48-
use std::sync::atomic::AtomicU8;
49-
use std::sync::{Arc, atomic::Ordering::Relaxed, mpsc};
50+
use std::sync::mpsc;
5051
use std::thread;
5152
use std::time::{Duration, Instant};
5253

@@ -86,72 +87,6 @@ struct Settings {
8687
buffered: bool,
8788
}
8889

89-
/// A timer which triggers on a given interval
90-
///
91-
/// After being constructed with [`Alarm::with_interval`], [`Alarm::get_trigger`]
92-
/// will return [`ALARM_TRIGGER_TIMER`] once per the given [`Duration`].
93-
/// Alarm can be manually triggered with closure returned by [`Alarm::manual_trigger_fn`].
94-
/// [`Alarm::get_trigger`] will return [`ALARM_TRIGGER_SIGNAL`] in this case.
95-
///
96-
/// Can be cloned, but the trigger status is shared across all instances so only
97-
/// the first caller each interval will yield true.
98-
///
99-
/// When all instances are dropped the background thread will exit on the next interval.
100-
pub struct Alarm {
101-
interval: Duration,
102-
trigger: Arc<AtomicU8>,
103-
}
104-
105-
pub const ALARM_TRIGGER_NONE: u8 = 0;
106-
pub const ALARM_TRIGGER_TIMER: u8 = 1;
107-
pub const ALARM_TRIGGER_SIGNAL: u8 = 2;
108-
109-
impl Alarm {
110-
/// use to construct alarm timer with duration
111-
pub fn with_interval(interval: Duration) -> Self {
112-
let trigger = Arc::new(AtomicU8::default());
113-
114-
let weak_trigger = Arc::downgrade(&trigger);
115-
thread::spawn(move || {
116-
while let Some(trigger) = weak_trigger.upgrade() {
117-
thread::sleep(interval);
118-
trigger.store(ALARM_TRIGGER_TIMER, Relaxed);
119-
}
120-
});
121-
122-
Self { interval, trigger }
123-
}
124-
125-
/// Returns a closure that allows to manually trigger the alarm
126-
///
127-
/// This is useful for cases where more than one alarm even source exists
128-
/// In case of `dd` there is the SIGUSR1/SIGINFO case where we want to
129-
/// trigger an manual progress report.
130-
pub fn manual_trigger_fn(&self) -> Box<dyn Send + Sync + Fn()> {
131-
let weak_trigger = Arc::downgrade(&self.trigger);
132-
Box::new(move || {
133-
if let Some(trigger) = weak_trigger.upgrade() {
134-
trigger.store(ALARM_TRIGGER_SIGNAL, Relaxed);
135-
}
136-
})
137-
}
138-
139-
/// Use this function to poll for any pending alarm event
140-
///
141-
/// Returns `ALARM_TRIGGER_NONE` for no pending event.
142-
/// Returns `ALARM_TRIGGER_TIMER` if the event was triggered by timer
143-
/// Returns `ALARM_TRIGGER_SIGNAL` if the event was triggered manually
144-
/// by the closure returned from `manual_trigger_fn`
145-
pub fn get_trigger(&self) -> u8 {
146-
self.trigger.swap(ALARM_TRIGGER_NONE, Relaxed)
147-
}
148-
149-
// Getter function for the configured interval duration
150-
pub fn get_interval(&self) -> Duration {
151-
self.interval
152-
}
153-
}
154-
15590
/// A number in blocks or bytes
15691
///
15792
/// Some values (seek, skip, iseek, oseek) can have values either in blocks or in bytes.
@@ -1177,20 +1112,11 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
11771112
// This is the max size needed.
11781113
let mut buf = vec![BUF_INIT_BYTE; bsize];
11791114

1180-
// Spawn a timer thread to provide a scheduled signal indicating when we
1181-
// should send an update of our progress to the reporting thread.
1182-
//
1183-
// This avoids the need to query the OS monotonic clock for every block.
1184-
let alarm = Alarm::with_interval(Duration::from_secs(1));
1115+
let mut next_progress_time = Instant::now() + Duration::from_secs(1);
11851116

1186-
// The signal handler spawns an own thread that waits for signals.
1187-
// When the signal is received, it calls a handler function.
1188-
// We inject a handler function that manually triggers the alarm.
11891117
#[cfg(target_os = "linux")]
1190-
let signal_handler = progress::SignalHandler::install_signal_handler(alarm.manual_trigger_fn());
1191-
#[cfg(target_os = "linux")]
1192-
if let Err(e) = &signal_handler {
1193-
if Some(StatusLevel::None) != i.settings.status {
1118+
if let Err(e) = install_sigusr1_handler() {
1119+
if i.settings.status != Some(StatusLevel::None) {
11941120
eprintln!("{}\n\t{e}", translate!("dd-warning-signal-handler"));
11951121
}
11961122
}
@@ -1270,17 +1196,18 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
12701196
// error.
12711197
rstat += rstat_update;
12721198
wstat += wstat_update;
1273-
match alarm.get_trigger() {
1274-
ALARM_TRIGGER_NONE => {}
1275-
t @ (ALARM_TRIGGER_TIMER | ALARM_TRIGGER_SIGNAL) => {
1276-
let tp = match t {
1277-
ALARM_TRIGGER_TIMER => ProgUpdateType::Periodic,
1278-
_ => ProgUpdateType::Signal,
1279-
};
1280-
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), tp);
1281-
prog_tx.send(prog_update).unwrap_or(());
1282-
}
1283-
_ => {}
1199+
1200+
#[cfg(target_os = "linux")]
1201+
if check_and_reset_sigusr1() {
1202+
let prog_update =
1203+
ProgUpdate::new(rstat, wstat, start.elapsed(), ProgUpdateType::Signal);
1204+
prog_tx.send(prog_update).unwrap_or(());
1205+
}
1206+
if Instant::now() >= next_progress_time {
1207+
let prog_update =
1208+
ProgUpdate::new(rstat, wstat, start.elapsed(), ProgUpdateType::Periodic);
1209+
prog_tx.send(prog_update).unwrap_or(());
1210+
next_progress_time += Duration::from_secs(1);
12841211
}
12851212
}
12861213

src/uu/dd/src/progress.rs

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@
1010
//! [`gen_prog_updater`] function can be used to implement a progress
1111
//! updater that runs in its own thread.
1212
use std::io::Write;
13-
use std::sync::mpsc;
1413
#[cfg(target_os = "linux")]
15-
use std::thread::JoinHandle;
14+
use std::sync::atomic::{AtomicBool, Ordering};
15+
use std::sync::mpsc;
1616
use std::time::Duration;
17-
18-
#[cfg(target_os = "linux")]
19-
use signal_hook::iterator::Handle;
2017
use uucore::{
2118
error::{UResult, set_exit_code},
2219
format::num_format::{FloatVariant, Formatter},
@@ -448,47 +445,22 @@ pub(crate) fn gen_prog_updater(
448445
}
449446
}
450447

451-
/// signal handler listens for SIGUSR1 signal and runs provided closure.
452448
#[cfg(target_os = "linux")]
453-
pub(crate) struct SignalHandler {
454-
handle: Handle,
455-
thread: Option<JoinHandle<()>>,
456-
}
449+
static SIGUSR1_RECEIVED: AtomicBool = AtomicBool::new(false);
457450

458451
#[cfg(target_os = "linux")]
459-
impl SignalHandler {
460-
pub(crate) fn install_signal_handler(
461-
f: Box<dyn Send + Sync + Fn()>,
462-
) -> Result<Self, std::io::Error> {
463-
use signal_hook::consts::signal::SIGUSR1;
464-
use signal_hook::iterator::Signals;
465-
466-
let mut signals = Signals::new([SIGUSR1])?;
467-
let handle = signals.handle();
468-
let thread = std::thread::spawn(move || {
469-
for signal in &mut signals {
470-
match signal {
471-
SIGUSR1 => (*f)(),
472-
_ => unreachable!(),
473-
}
474-
}
475-
});
452+
pub(crate) fn check_and_reset_sigusr1() -> bool {
453+
SIGUSR1_RECEIVED.swap(false, Ordering::Relaxed)
454+
}
476455

477-
Ok(Self {
478-
handle,
479-
thread: Some(thread),
480-
})
481-
}
456+
#[cfg(target_os = "linux")]
457+
extern "C" fn sigusr1_handler(_: std::os::raw::c_int) {
458+
SIGUSR1_RECEIVED.store(true, Ordering::Relaxed);
482459
}
483460

484461
#[cfg(target_os = "linux")]
485-
impl Drop for SignalHandler {
486-
fn drop(&mut self) {
487-
self.handle.close();
488-
if let Some(thread) = std::mem::take(&mut self.thread) {
489-
thread.join().unwrap();
490-
}
491-
}
462+
pub(crate) fn install_sigusr1_handler() -> Result<(), nix::errno::Errno> {
463+
uucore::signals::install_signal_handler(nix::sys::signal::Signal::SIGUSR1, sigusr1_handler)
492464
}
493465

494466
/// Return a closure that can be used in its own thread to print progress info.

src/uucore/src/lib/features/signals.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
use nix::errno::Errno;
1515
#[cfg(unix)]
1616
use nix::sys::signal::{
17-
SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE, signal,
17+
SaFlags, SigAction, SigHandler, SigHandler::SigDfl, SigHandler::SigIgn, SigSet, Signal,
18+
Signal::SIGINT, Signal::SIGPIPE, sigaction, signal,
1819
};
1920

2021
/// The default signal value.
@@ -435,6 +436,21 @@ pub fn ignore_interrupts() -> Result<(), Errno> {
435436
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
436437
}
437438

439+
/// Installs a signal handler. The handler must be async-signal-safe.
440+
#[cfg(unix)]
441+
pub fn install_signal_handler(
442+
sig: Signal,
443+
handler: extern "C" fn(std::os::raw::c_int),
444+
) -> Result<(), Errno> {
445+
let action = SigAction::new(
446+
SigHandler::Handler(handler),
447+
SaFlags::SA_RESTART,
448+
SigSet::empty(),
449+
);
450+
unsafe { sigaction(sig, &action) }?;
451+
Ok(())
452+
}
453+
438454
// Detect closed stdin/stdout before Rust reopens them as /dev/null (see issue #2873)
439455
#[cfg(unix)]
440456
use std::sync::atomic::{AtomicBool, Ordering};

0 commit comments

Comments
 (0)