fix(proto): re-arm PathIdle timer when idle timeout is changed (#544)

## Description

Immediately re-arm or stop the PathIdle timer when
`set_path_max_idle_timeout` is called, accounting for already-elapsed
idle time. Previously the running timer continued with the old deadline,
so extending the timeout during a network change had no effect until the
next received packet.

Setting `None` now stops the timer.

Split from #525.

Relates to #376

---------

Co-authored-by: Philipp Krüger <philipp.krueger1@gmail.com>
This commit is contained in:
Friedel Ziegelmayer
2026-03-26 13:10:44 +01:00
committed by GitHub
parent 2622b67748
commit cdde4e92f3
4 changed files with 43 additions and 5 deletions
+28 -2
View File
@@ -843,13 +843,17 @@ impl Connection {
self.path(path_id).and_then(|path| path.remote_status())
}
/// Sets the max_idle_timeout for a specific path
/// Sets the max_idle_timeout for a specific path.
///
/// The PathIdle timer is immediately re-armed accounting for already-elapsed
/// idle time. Setting `None` disables the timeout and stops the timer.
///
/// See [`TransportConfig::default_path_max_idle_timeout`] for details.
///
/// Returns the previous value of the setting.
pub fn set_path_max_idle_timeout(
&mut self,
now: Instant,
path_id: PathId,
timeout: Option<Duration>,
) -> Result<Option<Duration>, ClosedPath> {
@@ -857,7 +861,29 @@ impl Connection {
.paths
.get_mut(&path_id)
.ok_or(ClosedPath { _private: () })?;
Ok(std::mem::replace(&mut path.data.idle_timeout, timeout))
let prev = std::mem::replace(&mut path.data.idle_timeout, timeout);
// Adjust the PathIdle timer, accounting for already-elapsed idle time.
if !self.state.is_closed() {
if let Some(new_timeout) = timeout {
let timer = Timer::PerPath(path_id, PathTimer::PathIdle);
let deadline = match (prev, self.timers.get(timer)) {
(Some(old_timeout), Some(old_deadline)) => {
let last_activity = old_deadline.checked_sub(old_timeout).unwrap_or(now);
last_activity + new_timeout
}
_ => now + new_timeout,
};
self.timers.set(timer, deadline, self.qlog.with_time(now));
} else {
self.timers.stop(
Timer::PerPath(path_id, PathTimer::PathIdle),
self.qlog.with_time(now),
);
}
}
Ok(prev)
}
/// Sets the keep_alive_interval for a specific path
+11 -1
View File
@@ -155,7 +155,6 @@ where
self.heap.as_mut().and_then(|h| h.remove(key))
}
#[cfg(test)]
fn get(&self, key: &K) -> Option<&V> {
for (k, v) in self.stack.iter().filter_map(|v| v.as_ref()) {
if k == key {
@@ -240,6 +239,10 @@ impl PathTimerTable {
self.timers[timer as usize] = Some(time);
}
fn get(&self, timer: PathTimer) -> Option<Instant> {
self.timers[timer as usize]
}
fn stop(&mut self, timer: PathTimer) {
self.timers[timer as usize] = None;
}
@@ -281,6 +284,13 @@ impl TimerTable {
qlog.emit_timer_set(timer, time);
}
pub(super) fn get(&self, timer: Timer) -> Option<Instant> {
match timer {
Timer::Conn(timer) => self.generic[timer as usize],
Timer::PerPath(path_id, timer) => self.path_timers.get(&path_id)?.get(timer),
}
}
pub(super) fn set_or_stop(
&mut self,
timer: Timer,
+2 -1
View File
@@ -564,8 +564,9 @@ impl ConnPair {
path_id: PathId,
timeout: Option<Duration>,
) -> Result<Option<Duration>, ClosedPath> {
let now = self.pair.time;
self.conn_mut(side)
.set_path_max_idle_timeout(path_id, timeout)
.set_path_max_idle_timeout(now, path_id, timeout)
}
pub(super) fn set_path_keep_alive_interval(
+2 -1
View File
@@ -241,7 +241,8 @@ impl Path {
timeout: Option<Duration>,
) -> Result<Option<Duration>, ClosedPath> {
let mut state = self.conn.state.lock("path_set_max_idle_timeout");
state.inner.set_path_max_idle_timeout(self.id, timeout)
let now = state.runtime.now();
state.inner.set_path_max_idle_timeout(now, self.id, timeout)
}
/// Sets the keep_alive_interval for a specific path