Skip to content

Commit 01cd05e

Browse files
Address suspend rail review feedback
1 parent d805263 commit 01cd05e

2 files changed

Lines changed: 150 additions & 19 deletions

File tree

crates/lg-buddy/src/session/runner.rs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,13 @@ fn run_lifecycle_monitor_with_bus<W: Write, E: SessionActionExecutor>(
352352
)?;
353353

354354
let mut sleep_inhibitor = None;
355-
acquire_lifecycle_sleep_delay_inhibitor_if_enabled(
355+
let mut waiting_for_resume = false;
356+
sync_lifecycle_sleep_delay_inhibitor_if_idle(
356357
writer,
357358
bus,
358359
config_path,
359360
&mut sleep_inhibitor,
361+
waiting_for_resume,
360362
)?;
361363

362364
let started = Instant::now();
@@ -378,6 +380,14 @@ fn run_lifecycle_monitor_with_bus<W: Write, E: SessionActionExecutor>(
378380
.min(timeout.saturating_sub(started.elapsed()));
379381
}
380382

383+
sync_lifecycle_sleep_delay_inhibitor_if_idle(
384+
writer,
385+
bus,
386+
config_path,
387+
&mut sleep_inhibitor,
388+
waiting_for_resume,
389+
)?;
390+
381391
let Some(signal) =
382392
bus.process(process_timeout)
383393
.map_err(|err| SessionRunnerError::Failed {
@@ -393,6 +403,12 @@ fn run_lifecycle_monitor_with_bus<W: Write, E: SessionActionExecutor>(
393403
};
394404

395405
let event_kind = LifecycleEvent::from_runtime_event(event);
406+
match event_kind {
407+
Some(LifecycleEvent::MachinePreparingForSleep) => waiting_for_resume = true,
408+
Some(LifecycleEvent::MachineResumed) => waiting_for_resume = false,
409+
_ => {}
410+
}
411+
396412
if !lifecycle_policy_enabled_from_config(config_path)? {
397413
release_lifecycle_sleep_delay_inhibitor(writer, &mut sleep_inhibitor)?;
398414
writeln!(
@@ -409,11 +425,12 @@ fn run_lifecycle_monitor_with_bus<W: Write, E: SessionActionExecutor>(
409425
match event_kind {
410426
Some(LifecycleEvent::MachineResumed) => {
411427
dispatcher.dispatch_lifecycle_event(writer, event)?;
412-
acquire_lifecycle_sleep_delay_inhibitor_if_enabled(
428+
sync_lifecycle_sleep_delay_inhibitor_if_idle(
413429
writer,
414430
bus,
415431
config_path,
416432
&mut sleep_inhibitor,
433+
waiting_for_resume,
417434
)?;
418435
}
419436
Some(LifecycleEvent::MachinePreparingForSleep) => {
@@ -436,13 +453,19 @@ fn run_lifecycle_monitor_with_bus<W: Write, E: SessionActionExecutor>(
436453
}
437454
}
438455

439-
fn acquire_lifecycle_sleep_delay_inhibitor_if_enabled<W: Write>(
456+
fn sync_lifecycle_sleep_delay_inhibitor_if_idle<W: Write>(
440457
writer: &mut W,
441458
bus: &mut impl SessionBusClient,
442459
config_path: &Path,
443460
sleep_inhibitor: &mut Option<OwnedFd>,
461+
waiting_for_resume: bool,
444462
) -> Result<(), SessionRunnerError> {
445-
if sleep_inhibitor.is_some() || !lifecycle_policy_enabled_from_config(config_path)? {
463+
if !lifecycle_policy_enabled_from_config(config_path)? {
464+
release_lifecycle_sleep_delay_inhibitor(writer, sleep_inhibitor)?;
465+
return Ok(());
466+
}
467+
468+
if waiting_for_resume || sleep_inhibitor.is_some() {
446469
return Ok(());
447470
}
448471

@@ -1572,6 +1595,7 @@ mod tests {
15721595
method_calls: Vec<(String, String, String, String)>,
15731596
inhibitor_write_fds: Vec<i32>,
15741597
disable_config_after_signals: Option<PathBuf>,
1598+
enable_config_after_idle: Option<PathBuf>,
15751599
}
15761600

15771601
impl SessionBusClient for FakeSessionBus {
@@ -1636,6 +1660,9 @@ mod tests {
16361660
if let Some(config_path) = self.disable_config_after_signals.take() {
16371661
write_lifecycle_config(&config_path, "disabled");
16381662
}
1663+
if let Some(config_path) = self.enable_config_after_idle.take() {
1664+
write_lifecycle_config(&config_path, "enabled");
1665+
}
16391666

16401667
Ok(None)
16411668
}
@@ -1806,6 +1833,7 @@ system_sleep_wake_policy={policy}
18061833
method_calls: Vec::new(),
18071834
inhibitor_write_fds: Vec::new(),
18081835
disable_config_after_signals: Some(config_path.clone()),
1836+
enable_config_after_idle: None,
18091837
};
18101838
let mut output = Vec::new();
18111839

@@ -1854,6 +1882,7 @@ system_sleep_wake_policy={policy}
18541882
method_calls: Vec::new(),
18551883
inhibitor_write_fds: Vec::new(),
18561884
disable_config_after_signals: None,
1885+
enable_config_after_idle: None,
18571886
};
18581887
let mut output = Vec::new();
18591888

@@ -1871,6 +1900,45 @@ system_sleep_wake_policy={policy}
18711900
std::env::remove_var(super::LIFECYCLE_MONITOR_TEST_EVENT_LIMIT_ENV);
18721901
}
18731902

1903+
#[test]
1904+
fn lifecycle_monitor_reacquires_inhibitor_when_policy_is_reenabled_while_idle() {
1905+
let _guard = env_lock()
1906+
.lock()
1907+
.unwrap_or_else(|poisoned| poisoned.into_inner());
1908+
std::env::set_var(super::LIFECYCLE_MONITOR_TEST_TIMEOUT_SECS_ENV, "0.2");
1909+
1910+
let config_path = unique_config_path("lifecycle-reenabled");
1911+
write_lifecycle_config(&config_path, "disabled");
1912+
let executor = FakeActionExecutor::default();
1913+
let mut bus = FakeLifecycleBus {
1914+
signals: VecDeque::new(),
1915+
signal_match_count: 0,
1916+
method_calls: Vec::new(),
1917+
inhibitor_write_fds: Vec::new(),
1918+
disable_config_after_signals: None,
1919+
enable_config_after_idle: Some(config_path.clone()),
1920+
};
1921+
let mut output = Vec::new();
1922+
1923+
run_lifecycle_monitor_with_bus(&mut output, executor, &config_path, &mut bus)
1924+
.expect("lifecycle loop exits cleanly after test timeout");
1925+
1926+
let output = String::from_utf8(output).expect("utf8");
1927+
assert!(output.contains("Using logind system lifecycle source"));
1928+
assert!(output.contains("Acquired logind sleep delay inhibitor"));
1929+
assert_eq!(bus.signal_match_count, 1);
1930+
assert_eq!(
1931+
bus.method_calls
1932+
.iter()
1933+
.map(|(_, _, _, member)| member.as_str())
1934+
.collect::<Vec<_>>(),
1935+
vec!["Inhibit"]
1936+
);
1937+
assert!(bus.enable_config_after_idle.is_none());
1938+
fs::remove_file(config_path).expect("remove lifecycle test config");
1939+
std::env::remove_var(super::LIFECYCLE_MONITOR_TEST_TIMEOUT_SECS_ENV);
1940+
}
1941+
18741942
#[test]
18751943
fn unhandled_events_are_logged_without_running_actions() {
18761944
let executor = FakeActionExecutor::default();

crates/lg-buddy/src/state.rs

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::os::fd::AsRawFd;
99
#[cfg(unix)]
1010
use std::os::unix::fs::OpenOptionsExt;
1111
use std::path::{Path, PathBuf};
12+
use std::process;
1213
use std::time::{Duration, SystemTime};
1314

1415
const STATE_DIR_NAME: &str = "lg_buddy";
@@ -364,20 +365,54 @@ fn create_marker_file(path: &Path) -> io::Result<()> {
364365
fs::write(path, [])
365366
}
366367

367-
#[cfg(unix)]
368368
fn write_state_file(path: &Path, content: &str) -> io::Result<()> {
369-
let mut file = OpenOptions::new()
370-
.write(true)
371-
.create(true)
372-
.truncate(true)
373-
.custom_flags(libc::O_NOFOLLOW)
374-
.open(path)?;
375-
file.write_all(content.as_bytes())
369+
let mut last_error = None;
370+
for attempt in 0..100 {
371+
let temp_path = state_temp_path(path, attempt);
372+
let mut options = OpenOptions::new();
373+
options.write(true).create_new(true);
374+
#[cfg(unix)]
375+
options.custom_flags(libc::O_NOFOLLOW);
376+
377+
let mut file = match options.open(&temp_path) {
378+
Ok(file) => file,
379+
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
380+
last_error = Some(err);
381+
continue;
382+
}
383+
Err(err) => return Err(err),
384+
};
385+
386+
let result = (|| {
387+
file.write_all(content.as_bytes())?;
388+
file.flush()?;
389+
file.sync_all()?;
390+
drop(file);
391+
fs::rename(&temp_path, path)
392+
})();
393+
394+
if let Err(err) = result {
395+
let _ = fs::remove_file(&temp_path);
396+
return Err(err);
397+
}
398+
399+
return Ok(());
400+
}
401+
402+
Err(last_error.unwrap_or_else(|| {
403+
io::Error::new(
404+
io::ErrorKind::AlreadyExists,
405+
"could not create unique state temporary file",
406+
)
407+
}))
376408
}
377409

378-
#[cfg(not(unix))]
379-
fn write_state_file(path: &Path, content: &str) -> io::Result<()> {
380-
fs::write(path, content)
410+
fn state_temp_path(path: &Path, attempt: u8) -> PathBuf {
411+
let file_name = path
412+
.file_name()
413+
.and_then(|name| name.to_str())
414+
.unwrap_or("state");
415+
path.with_file_name(format!(".{file_name}.{}.{}.tmp", process::id(), attempt))
381416
}
382417

383418
#[cfg(unix)]
@@ -397,10 +432,10 @@ fn current_uid() -> Option<u32> {
397432
#[cfg(test)]
398433
mod tests {
399434
use super::{
400-
resolve_state_dir, RuntimeDirSources, ScreenOwnershipMarker, StateDirError, StateScope,
401-
SystemSleepAttemptState, SystemSleepCycleOutcome, SystemSleepCycleState,
402-
SCREEN_OFF_BY_US_MARKER, SYSTEM_SLEEP_ATTEMPTED_MARKER, SYSTEM_SLEEP_ATTEMPT_LOCK,
403-
SYSTEM_SLEEP_CYCLE_STATE,
435+
resolve_state_dir, state_temp_path, RuntimeDirSources, ScreenOwnershipMarker,
436+
StateDirError, StateScope, SystemSleepAttemptState, SystemSleepCycleOutcome,
437+
SystemSleepCycleState, SCREEN_OFF_BY_US_MARKER, SYSTEM_SLEEP_ATTEMPTED_MARKER,
438+
SYSTEM_SLEEP_ATTEMPT_LOCK, SYSTEM_SLEEP_CYCLE_STATE,
404439
};
405440
use std::fs;
406441
#[cfg(unix)]
@@ -565,6 +600,34 @@ mod tests {
565600
assert_eq!(state.read_outcome().expect("read cleared outcome"), None);
566601
}
567602

603+
#[test]
604+
fn system_sleep_cycle_outcome_failed_atomic_write_preserves_existing_file() {
605+
let temp_dir = TestDir::new("system-sleep-cycle-atomic-failure");
606+
let state = SystemSleepCycleState::new(temp_dir.path().to_path_buf());
607+
608+
state
609+
.write_outcome(SystemSleepCycleOutcome::InProgress)
610+
.expect("write initial outcome");
611+
for attempt in 0..100 {
612+
fs::write(state_temp_path(state.cycle_path(), attempt), "occupied")
613+
.expect("occupy atomic temp path");
614+
}
615+
616+
let err = state
617+
.write_outcome(SystemSleepCycleOutcome::Completed)
618+
.expect_err("colliding temp paths should fail");
619+
620+
assert_eq!(err.kind(), std::io::ErrorKind::AlreadyExists);
621+
assert_eq!(
622+
fs::read_to_string(state.cycle_path()).expect("read preserved outcome file"),
623+
"outcome=in_progress\n"
624+
);
625+
assert_eq!(
626+
state.read_outcome().expect("read preserved outcome"),
627+
Some(SystemSleepCycleOutcome::InProgress)
628+
);
629+
}
630+
568631
#[test]
569632
fn system_sleep_cycle_outcome_rejects_unknown_value() {
570633
let temp_dir = TestDir::new("system-sleep-cycle-bad-outcome");

0 commit comments

Comments
 (0)