Fix session timeout due to timestamp frequency mismatch and activity tracking (#1106)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
This commit is contained in:
Copilot 2025-12-15 16:21:21 -05:00 • committed by GitHub
commit aaffd71c69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 1 deletions

View file

@ -18,7 +18,7 @@ internal sealed partial class StatefulSessionManager(
private readonly TimeProvider _timeProvider = httpServerTransportOptions.Value.TimeProvider;
private readonly TimeSpan _idleTimeout = httpServerTransportOptions.Value.IdleTimeout;
private readonly long _idleTimeoutTicks = httpServerTransportOptions.Value.IdleTimeout.Ticks;
private readonly long _idleTimeoutTicks = GetIdleTimeoutInTimestampTicks(httpServerTransportOptions.Value.IdleTimeout, httpServerTransportOptions.Value.TimeProvider);
private readonly int _maxIdleSessionCount = httpServerTransportOptions.Value.MaxIdleSessionCount;
private readonly object _idlePruningLock = new();
@ -229,6 +229,15 @@ internal sealed partial class StatefulSessionManager(
}
}
private static long GetIdleTimeoutInTimestampTicks(TimeSpan idleTimeout, TimeProvider timeProvider)
{
// Convert TimeSpan.Ticks (100-nanosecond intervals) to timestamp ticks based on TimeProvider.TimestampFrequency.
// TimeSpan.Ticks uses a fixed frequency of 10,000,000 ticks per second (100ns intervals).
// TimeProvider.GetTimestamp() returns ticks based on TimeProvider.TimestampFrequency, which varies by platform
// (e.g., ~1,000,000,000 on macOS using nanoseconds, ~10,000,000 on Windows using 100ns intervals).
return (long)(idleTimeout.Ticks * timeProvider.TimestampFrequency / (double)TimeSpan.TicksPerSecond);
}
[LoggerMessage(Level = LogLevel.Information, Message = "IdleTimeout of {IdleTimeout} exceeded. Closing idle session {SessionId}.")]
private partial void LogIdleSessionTimeout(string sessionId, TimeSpan idleTimeout);

View file

@ -58,6 +58,8 @@ internal sealed class StreamableHttpSession(
{
sessionManager.DecrementIdleSessionCount();
}
// Update LastActivityTicks when acquiring reference in Started state to prevent timeout during active usage
LastActivityTicks = sessionManager.TimeProvider.GetTimestamp();
break;
case SessionState.Disposed:
throw new ObjectDisposedException(nameof(StreamableHttpSession));