Skip to content

Commit d07bf10

Browse files
authored
fix: use saturating_sub to prevent overflow panics in runtime metrics (#114)
1 parent 8d6603a commit d07bf10

3 files changed

Lines changed: 23 additions & 17 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(
2+
clippy::arithmetic_side_effects,
23
missing_debug_implementations,
34
missing_docs,
45
rust_2018_idioms,

src/runtime.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ impl RuntimeIntervals {
12681268
let mut metrics = RuntimeMetrics {
12691269
workers_count: self.runtime.num_workers(),
12701270
live_tasks_count: self.runtime.num_alive_tasks(),
1271-
elapsed: now - self.started_at,
1271+
elapsed: now.saturating_duration_since(self.started_at),
12721272
global_queue_depth: self.runtime.global_queue_depth(),
12731273
min_park_count: u64::MAX,
12741274
min_busy_duration: Duration::from_secs(1000000000),
@@ -1281,7 +1281,7 @@ impl RuntimeIntervals {
12811281
let budget_forced_yields = self.runtime.budget_forced_yield_count();
12821282
let io_driver_ready_events = self.runtime.io_driver_ready_count();
12831283

1284-
metrics.num_remote_schedules = num_remote_schedules - self.num_remote_schedules;
1284+
metrics.num_remote_schedules = num_remote_schedules.saturating_sub(self.num_remote_schedules);
12851285
metrics.min_noop_count = u64::MAX;
12861286
metrics.min_steal_count = u64::MAX;
12871287
metrics.min_local_schedule_count = u64::MAX;
@@ -1291,8 +1291,8 @@ impl RuntimeIntervals {
12911291
metrics.mean_poll_duration_worker_min = Duration::MAX;
12921292
metrics.poll_time_histogram = vec![0; self.runtime.poll_time_histogram_num_buckets()];
12931293
metrics.budget_forced_yield_count =
1294-
budget_forced_yields - self.budget_forced_yield_count;
1295-
metrics.io_driver_ready_count = io_driver_ready_events - self.io_driver_ready_count;
1294+
budget_forced_yields.saturating_sub(self.budget_forced_yield_count);
1295+
metrics.io_driver_ready_count = io_driver_ready_events.saturating_sub(self.io_driver_ready_count);
12961296

12971297
self.num_remote_schedules = num_remote_schedules;
12981298
self.budget_forced_yield_count = budget_forced_yields;
@@ -1506,7 +1506,7 @@ impl Worker {
15061506
);
15071507

15081508
// Get the number of polls since last probe
1509-
worker_polls_count = self.total_polls_count - worker_polls_count;
1509+
worker_polls_count = self.total_polls_count.saturating_sub(worker_polls_count);
15101510

15111511
// Update the mean task poll duration if there were polls
15121512
if worker_polls_count > 0 {
@@ -1535,16 +1535,16 @@ impl Worker {
15351535
if worker_polls_count > 0 {
15361536
for (bucket, cell) in metrics.poll_time_histogram.iter_mut().enumerate() {
15371537
let new = rt.poll_time_histogram_bucket_count(self.worker, bucket);
1538-
let delta = new - self.poll_time_histogram[bucket];
1538+
let delta = new.saturating_sub(self.poll_time_histogram[bucket]);
15391539
self.poll_time_histogram[bucket] = new;
15401540

1541-
*cell += delta;
1541+
*cell = cell.saturating_add(delta);
15421542
}
15431543
}
15441544

15451545
// Local scheduled tasks is an absolute value
15461546
let local_scheduled_tasks = rt.worker_local_queue_depth(self.worker);
1547-
metrics.total_local_queue_depth += local_scheduled_tasks;
1547+
metrics.total_local_queue_depth = metrics.total_local_queue_depth.saturating_add(local_scheduled_tasks);
15481548

15491549
if local_scheduled_tasks > metrics.max_local_queue_depth {
15501550
metrics.max_local_queue_depth = local_scheduled_tasks;
@@ -1574,7 +1574,7 @@ derived_metrics!(
15741574
unstable {
15751575
/// Returns the ratio of the [`RuntimeMetrics::total_polls_count`] to the [`RuntimeMetrics::total_noop_count`].
15761576
pub fn mean_polls_per_park(&self) -> f64 {
1577-
let total_park_count = self.total_park_count - self.total_noop_count;
1577+
let total_park_count = self.total_park_count.saturating_sub(self.total_noop_count);
15781578
if total_park_count == 0 {
15791579
0.0
15801580
} else {

src/task.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,8 +1877,8 @@ impl RawMetrics {
18771877
let total_slow_poll_duration =
18781878
Duration::from_nanos(self.total_slow_poll_duration.load(SeqCst));
18791879

1880-
let total_poll_count = total_fast_poll_count + total_slow_poll_count;
1881-
let total_poll_duration = total_fast_poll_duration + total_slow_poll_duration;
1880+
let total_poll_count = total_fast_poll_count.saturating_add(total_slow_poll_count);
1881+
let total_poll_duration = total_fast_poll_duration.saturating_add(total_slow_poll_duration);
18821882

18831883
TaskMetrics {
18841884
instrumented_count: self.instrumented_count.load(SeqCst),
@@ -2458,7 +2458,8 @@ fn instrument_poll<T, Out>(
24582458
/* 2. account for the time-to-first-poll of this task */
24592459
// if the time-to-first-poll of this task exceeds `u64::MAX` ns,
24602460
// round down to `u64::MAX` nanoseconds
2461-
let elapsed = (poll_start - instrumented_at)
2461+
let elapsed = poll_start
2462+
.saturating_duration_since(instrumented_at)
24622463
.as_nanos()
24632464
.try_into()
24642465
.unwrap_or(u64::MAX);
@@ -2478,7 +2479,7 @@ fn instrument_poll<T, Out>(
24782479
metrics.total_idled_count.fetch_add(1, SeqCst);
24792480

24802481
// compute the duration of the idle
2481-
let idle_ns = woke_at - *idled_at;
2482+
let idle_ns = woke_at.saturating_sub(*idled_at);
24822483

24832484
// update the max time tasks spent idling, both locally and
24842485
// globally.
@@ -2500,11 +2501,14 @@ fn instrument_poll<T, Out>(
25002501
// recall that the `woke_at` field is internally represented as
25012502
// nanoseconds-since-instrumentation. here, for accounting purposes,
25022503
// we need to instead represent it as a proper `Instant`.
2503-
let woke_instant = instrumented_at + Duration::from_nanos(woke_at);
2504+
let woke_instant = instrumented_at
2505+
.checked_add(Duration::from_nanos(woke_at))
2506+
.unwrap_or(poll_start);
25042507

25052508
// the duration this task spent scheduled is time time elapsed between
25062509
// when this task was awoke, and when it was polled.
2507-
let scheduled_ns = (poll_start - woke_instant)
2510+
let scheduled_ns = poll_start
2511+
.saturating_duration_since(woke_instant)
25082512
.as_nanos()
25092513
.try_into()
25102514
.unwrap_or(u64::MAX);
@@ -2536,12 +2540,13 @@ fn instrument_poll<T, Out>(
25362540
let ret = poll_fn(this.task, &mut cx);
25372541
let inner_poll_end = Instant::now();
25382542
/* idle time starts now */
2539-
*idled_at = (inner_poll_end - instrumented_at)
2543+
*idled_at = inner_poll_end
2544+
.saturating_duration_since(instrumented_at)
25402545
.as_nanos()
25412546
.try_into()
25422547
.unwrap_or(u64::MAX);
25432548
/* accounting for poll time */
2544-
let inner_poll_duration = inner_poll_end - inner_poll_start;
2549+
let inner_poll_duration = inner_poll_end.saturating_duration_since(inner_poll_start);
25452550
let inner_poll_ns: u64 = inner_poll_duration
25462551
.as_nanos()
25472552
.try_into()

0 commit comments

Comments
 (0)