diff --git a/noq-proto/src/connection/mod.rs b/noq-proto/src/connection/mod.rs index 1265e550d..8b0edc5e8 100644 --- a/noq-proto/src/connection/mod.rs +++ b/noq-proto/src/connection/mod.rs @@ -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, ) -> Result, 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 diff --git a/noq-proto/src/connection/timer.rs b/noq-proto/src/connection/timer.rs index fd211ea83..b3eadc439 100644 --- a/noq-proto/src/connection/timer.rs +++ b/noq-proto/src/connection/timer.rs @@ -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 { + 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 { + 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, diff --git a/noq-proto/src/tests/util.rs b/noq-proto/src/tests/util.rs index 830da9a38..c703e7557 100644 --- a/noq-proto/src/tests/util.rs +++ b/noq-proto/src/tests/util.rs @@ -564,8 +564,9 @@ impl ConnPair { path_id: PathId, timeout: Option, ) -> Result, 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( diff --git a/noq/src/path.rs b/noq/src/path.rs index ca4bd829d..1047f9485 100644 --- a/noq/src/path.rs +++ b/noq/src/path.rs @@ -241,7 +241,8 @@ impl Path { timeout: Option, ) -> Result, 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