Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ strip = "none"

[profile.bench]
strip = "debuginfo"

[patch.crates-io]
vmm-sys-util = { git = "https://github.com/roypat/vmm-sys-util", branch = "timerfd-nonblock-0.14" }
1 change: 0 additions & 1 deletion src/firecracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.136"
serde_json = "1.0.143"
thiserror = "2.0.16"
timerfd = "1.6.0"
utils = { path = "../utils" }
vmm = { path = "../vmm" }
vmm-sys-util = { version = "0.14.0", features = ["with-serde"] }
Expand Down
16 changes: 8 additions & 8 deletions src/firecracker/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use std::os::unix::io::AsRawFd;
use std::time::Duration;

use event_manager::{EventOps, Events, MutEventSubscriber};
use timerfd::{ClockId, SetTimeFlags, TimerFd, TimerState};
use vmm::logger::{IncMetric, METRICS, error, warn};
use vmm_sys_util::epoll::EventSet;
use vmm_sys_util::timerfd::{TimerFd, TimerFdFlag};

/// Metrics reporting period.
pub(crate) const WRITE_METRICS_PERIOD_MS: u64 = 60000;
Expand All @@ -23,7 +23,7 @@ pub(crate) struct PeriodicMetrics {
impl PeriodicMetrics {
/// PeriodicMetrics constructor. Can panic on `TimerFd` creation failure.
pub fn new() -> Self {
let write_metrics_event_fd = TimerFd::new_custom(ClockId::Monotonic, true, true)
let write_metrics_event_fd = TimerFd::new_with_flags(TimerFdFlag::NONBLOCK)
.expect("Cannot create the metrics timer fd.");
PeriodicMetrics {
write_metrics_event_fd,
Expand All @@ -35,12 +35,10 @@ impl PeriodicMetrics {
/// Start the periodic metrics engine which will flush metrics every `interval_ms` millisecs.
pub(crate) fn start(&mut self, interval_ms: u64) {
// Arm the log write timer.
let timer_state = TimerState::Periodic {
current: Duration::from_millis(interval_ms),
interval: Duration::from_millis(interval_ms),
};
let duration = Duration::from_millis(interval_ms);
self.write_metrics_event_fd
.set_state(timer_state, SetTimeFlags::Default);
.reset(duration, Some(duration))
.expect("failed to arm metrics write timer");

// Write the metrics straight away to check the process startup time.
self.write_metrics();
Expand Down Expand Up @@ -77,7 +75,9 @@ impl MutEventSubscriber for PeriodicMetrics {
}

if source == self.write_metrics_event_fd.as_raw_fd() {
self.write_metrics_event_fd.read();
self.write_metrics_event_fd
.wait()
.expect("failed to read metrics timer");
self.write_metrics();
} else {
error!("Spurious METRICS event!");
Expand Down
1 change: 0 additions & 1 deletion src/vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ serde = { version = "1.0.219", features = ["derive", "rc"] }
serde_json = "1.0.143"
slab = "0.4.11"
thiserror = "2.0.16"
timerfd = "1.5.0"
userfaultfd = "0.9.0"
utils = { path = "../utils" }
uuid = "1.18.0"
Expand Down
14 changes: 5 additions & 9 deletions src/vmm/src/devices/virtio/balloon/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::time::Duration;

use log::{error, info};
use serde::Serialize;
use timerfd::{ClockId, SetTimeFlags, TimerFd, TimerState};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::timerfd::{TimerFd, TimerFdFlag};

use super::super::ActivateError;
use super::super::device::{DeviceState, VirtioDevice};
Expand Down Expand Up @@ -215,7 +215,7 @@ impl Balloon {
}

let stats_timer =
TimerFd::new_custom(ClockId::Monotonic, true, true).map_err(BalloonError::Timer)?;
TimerFd::new_with_flags(TimerFdFlag::NONBLOCK).map_err(BalloonError::Timer)?;

Ok(Balloon {
avail_features,
Expand Down Expand Up @@ -259,7 +259,7 @@ impl Balloon {
}

pub(crate) fn process_stats_timer_event(&mut self) -> Result<(), BalloonError> {
self.stats_timer.read();
self.stats_timer.wait()?;
self.trigger_stats_update()
}

Expand Down Expand Up @@ -486,12 +486,8 @@ impl Balloon {
}

pub fn update_timer_state(&mut self) {
let timer_state = TimerState::Periodic {
current: Duration::from_secs(u64::from(self.stats_polling_interval_s)),
interval: Duration::from_secs(u64::from(self.stats_polling_interval_s)),
};
self.stats_timer
.set_state(timer_state, SetTimeFlags::Default);
let duration = Duration::from_secs(self.stats_polling_interval_s as u64);
self.stats_timer.reset(duration, Some(duration));
}

/// Obtain the number of 4K pages the device is currently holding.
Expand Down
2 changes: 1 addition & 1 deletion src/vmm/src/devices/virtio/balloon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub enum BalloonError {
/// {0}
InvalidAvailIdx(#[from] InvalidAvailIdx),
/// Error creating the statistics timer: {0}
Timer(std::io::Error),
Timer(#[from] vmm_sys_util::errno::Error),
}

#[derive(Debug, thiserror::Error, displaydoc::Display)]
Expand Down
10 changes: 2 additions & 8 deletions src/vmm/src/devices/virtio/balloon/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use std::sync::Arc;
use std::time::Duration;

use serde::{Deserialize, Serialize};
use timerfd::{SetTimeFlags, TimerState};

use super::*;
use crate::devices::virtio::balloon::device::{BalloonStats, ConfigSpace};
Expand Down Expand Up @@ -157,13 +156,8 @@ impl Persist<'_> for Balloon {
balloon.set_stats_desc_index(state.stats_desc_index);

// Restart timer if needed.
let timer_state = TimerState::Periodic {
current: Duration::from_secs(u64::from(state.stats_polling_interval_s)),
interval: Duration::from_secs(u64::from(state.stats_polling_interval_s)),
};
balloon
.stats_timer
.set_state(timer_state, SetTimeFlags::Default);
let duration = Duration::from_secs(state.stats_polling_interval_s as u64);
balloon.stats_timer.reset(duration, Some(duration));
}

Ok(balloon)
Expand Down
56 changes: 27 additions & 29 deletions src/vmm/src/rate_limiter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::time::{Duration, Instant};
use std::{fmt, io};

use timerfd::{ClockId, SetTimeFlags, TimerFd, TimerState};
use vmm_sys_util::timerfd::{TimerFd, TimerFdFlag};

pub mod persist;

#[derive(Debug, thiserror::Error, displaydoc::Display)]
/// Describes the errors that may occur while handling rate limiter events.
pub enum RateLimiterError {
/// The event handler was called spuriously: {0}
SpuriousRateLimiterEvent(&'static str),
/// Rate limiter event handler called without a present timer
SpuriousRateLimiterEvent,
/// Error reading timerfd: {0}
Read(#[from] vmm_sys_util::errno::Error),
}

// Interval at which the refill timer will run when limiter is at capacity.
const REFILL_TIMER_INTERVAL_MS: u64 = 100;
const TIMER_REFILL_STATE: TimerState =
TimerState::Oneshot(Duration::from_millis(REFILL_TIMER_INTERVAL_MS));
const REFILL_TIMER_DURATION: Duration = Duration::from_millis(100);

const NANOSEC_IN_ONE_MILLISEC: u64 = 1_000_000;

Expand Down Expand Up @@ -367,7 +367,7 @@ impl RateLimiter {
// We'll need a timer_fd, even if our current config effectively disables rate limiting,
// because `Self::update_buckets()` might re-enable it later, and we might be
// seccomp-blocked from creating the timer_fd at that time.
let timer_fd = TimerFd::new_custom(ClockId::Monotonic, true, true)?;
let timer_fd = TimerFd::new_with_flags(TimerFdFlag::NONBLOCK)?;

Ok(RateLimiter {
bandwidth: bytes_token_bucket,
Expand All @@ -378,9 +378,11 @@ impl RateLimiter {
}

// Arm the timer of the rate limiter with the provided `TimerState`.
fn activate_timer(&mut self, timer_state: TimerState) {
fn activate_timer(&mut self, one_shot_duration: Duration) {
// Register the timer; don't care about its previous state
self.timer_fd.set_state(timer_state, SetTimeFlags::Default);
self.timer_fd
.reset(one_shot_duration, None)
.expect("failed to activate ratelimiter timer");
self.timer_active = true;
}

Expand All @@ -407,7 +409,7 @@ impl RateLimiter {
// make sure there is only one running timer for this limiter.
BucketReduction::Failure => {
if !self.timer_active {
self.activate_timer(TIMER_REFILL_STATE);
self.activate_timer(REFILL_TIMER_DURATION);
}
false
}
Expand All @@ -424,9 +426,7 @@ impl RateLimiter {
// `ratio * refill_time` milliseconds.
// The conversion should be safe because the ratio is positive.
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
self.activate_timer(TimerState::Oneshot(Duration::from_millis(
(ratio * refill_time as f64) as u64,
)));
self.activate_timer(Duration::from_millis((ratio * refill_time as f64) as u64));
true
}
}
Expand Down Expand Up @@ -469,10 +469,8 @@ impl RateLimiter {
///
/// If the rate limiter is disabled or is not blocked, an error is returned.
pub fn event_handler(&mut self) -> Result<(), RateLimiterError> {
match self.timer_fd.read() {
0 => Err(RateLimiterError::SpuriousRateLimiterEvent(
"Rate limiter event handler called without a present timer",
)),
match self.timer_fd.wait()? {
0 => Err(RateLimiterError::SpuriousRateLimiterEvent),
_ => {
self.timer_active = false;
Ok(())
Expand Down Expand Up @@ -779,7 +777,7 @@ pub(crate) mod tests {
// second wait will always result in the limiter being refilled. Otherwise
// there is a chance for a race condition between limiter refilling and limiter
// checking.
const TEST_REFILL_TIMER_INTERVAL_MS: u64 = REFILL_TIMER_INTERVAL_MS + 10;
const TEST_REFILL_TIMER_DURATION: Duration = Duration::from_millis(110);

impl TokenBucket {
// Resets the token bucket: budget set to max capacity and last-updated set to now.
Expand Down Expand Up @@ -950,11 +948,11 @@ pub(crate) mod tests {
assert!(l.consume(u64::MAX, TokenType::Ops));
assert!(l.consume(u64::MAX, TokenType::Bytes));
// calling the handler without there having been an event should error
l.event_handler().unwrap_err();
assert_eq!(
format!("{:?}", l.event_handler().err().unwrap()),
"SpuriousRateLimiterEvent(\"Rate limiter event handler called without a present \
timer\")"
let err = l.event_handler().unwrap_err();
assert!(
matches!(err, RateLimiterError::SpuriousRateLimiterEvent),
"{:?}",
err
);
}

Expand Down Expand Up @@ -1016,11 +1014,11 @@ pub(crate) mod tests {
// since consume failed, limiter should be blocked now
assert!(l.is_blocked());
// wait half the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// limiter should still be blocked
assert!(l.is_blocked());
// wait the other half of the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// the timer_fd should have an event on it by now
l.event_handler().unwrap();
// limiter should now be unblocked
Expand Down Expand Up @@ -1049,11 +1047,11 @@ pub(crate) mod tests {
// since consume failed, limiter should be blocked now
assert!(l.is_blocked());
// wait half the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// limiter should still be blocked
assert!(l.is_blocked());
// wait the other half of the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// the timer_fd should have an event on it by now
l.event_handler().unwrap();
// limiter should now be unblocked
Expand Down Expand Up @@ -1083,11 +1081,11 @@ pub(crate) mod tests {
// since consume failed, limiter should be blocked now
assert!(l.is_blocked());
// wait half the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// limiter should still be blocked
assert!(l.is_blocked());
// wait the other half of the timer period
thread::sleep(Duration::from_millis(TEST_REFILL_TIMER_INTERVAL_MS / 2));
thread::sleep(TEST_REFILL_TIMER_DURATION / 2);
// the timer_fd should have an event on it by now
l.event_handler().unwrap();
// limiter should now be unblocked
Expand Down
13 changes: 4 additions & 9 deletions src/vmm/src/rate_limiter/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! Defines the structures needed for saving/restoring a RateLimiter.

use serde::{Deserialize, Serialize};
use vmm_sys_util::timerfd::{TimerFd, TimerFdFlag};

use super::*;
use crate::snapshot::Persist;
Expand Down Expand Up @@ -82,7 +83,7 @@ impl Persist<'_> for RateLimiter {
} else {
None
},
timer_fd: TimerFd::new_custom(ClockId::Monotonic, true, true)?,
timer_fd: TimerFd::new_with_flags(TimerFdFlag::NONBLOCK)?,
timer_active: false,
};

Expand Down Expand Up @@ -151,10 +152,7 @@ mod tests {
.unwrap()
.partial_eq(restored_rate_limiter.bandwidth().unwrap())
);
assert_eq!(
restored_rate_limiter.timer_fd.get_state(),
TimerState::Disarmed
);
assert!(!restored_rate_limiter.timer_fd.is_armed().unwrap());

// Check that RateLimiter restores correctly after partially consuming tokens.
rate_limiter.consume(10, TokenType::Bytes);
Expand All @@ -174,10 +172,7 @@ mod tests {
.unwrap()
.partial_eq(restored_rate_limiter.bandwidth().unwrap())
);
assert_eq!(
restored_rate_limiter.timer_fd.get_state(),
TimerState::Disarmed
);
assert!(!restored_rate_limiter.timer_fd.is_armed().unwrap());

// Check that RateLimiter restores correctly after totally consuming tokens.
rate_limiter.consume(1000, TokenType::Bytes);
Expand Down
Loading