Summary
TimeOutTask.RunWithTimeout<T1, TResult> (UtilitiesCS/Threading/TimeOutTask.cs:164-199) catches TimeoutException in its retry/timeout handling ladder, but the wrapped call is actually cancelled via a CancellationTokenSource-driven timeout, which surfaces as TaskCanceledException, not TimeoutException. The catch clause can never match a genuine timer-driven timeout in this overload.
Environment
- OS/version: n/a (pure C#/.NET Framework logic defect, reproducible on any platform)
- Python version: n/a
- Command/flags used: n/a
- Data source or fixture:
UtilitiesCS/Threading/TimeOutTask.cs
Steps to Reproduce
- Call
RunWithTimeout<T1, TResult> (the Func<T1, TResult> overload declared at TimeOutTask.cs:164) with a milliseconds value short enough that the internal CancellationTokenSource-linked Task.Run is cancelled by the timer before the delegate completes.
- Observe that the timer-driven cancellation raises
System.Threading.Tasks.TaskCanceledException, not System.TimeoutException.
- The
catch (TimeoutException) clause at line 199 does not match, so the intended retry/timeout-handling branch never executes for a real timeout; the exception instead propagates unhandled to the caller.
- Contrast with every sibling overload in the same file, which correctly catches
TaskCanceledException for the identical CancellationTokenSource-driven pattern: RunWithTimeout<TResult> (Func<TResult> overload) at line 64, RunWithTimeout<TResult> (Func<CancellationToken,Task<TResult>> overload) at line 129, RunWithTimeout<T1,T2,TResult> at line 350, RunWithTimeout<T1,T2,T3,TResult> at line 580.
Expected Behavior
A genuine timer-driven timeout in RunWithTimeout<T1, TResult> should be caught and handled by the same retry ladder as every sibling overload (catch (TaskCanceledException)), so maxAttempts/strict retry behavior applies consistently across all RunWithTimeout overloads.
Actual Behavior
catch (TimeoutException) at line 199 only matches a TimeoutException thrown directly by the wrapped delegate itself (an unusual, non-default case). It never matches the actual timer-driven TaskCanceledException that Task.Run(..., combinedToken) raises when the internal CancellationTokenSource fires. TaskCanceledException and TimeoutException both derive independently from SystemException and are unrelated types. As a result, the overload's own existing test coverage simulates "timeout" by having the wrapped delegate throw TimeoutException directly, rather than via a genuinely short milliseconds value - masking the defect in the existing test suite.
Logs / Screenshots
Impact / Severity
The retry/timeout-handling contract for this one overload silently does not work for its primary intended case (a real timer-driven timeout); callers relying on maxAttempts/strict retry-on-timeout behavior for RunWithTimeout<T1, TResult> get an unhandled TaskCanceledException instead of a retry.
Source
From: docs/features/potential/2026-07-09-timeouttask-runwithtimeout-exception-type-mismatch.md
Summary
TimeOutTask.RunWithTimeout<T1, TResult>(UtilitiesCS/Threading/TimeOutTask.cs:164-199) catchesTimeoutExceptionin its retry/timeout handling ladder, but the wrapped call is actually cancelled via aCancellationTokenSource-driven timeout, which surfaces asTaskCanceledException, notTimeoutException. The catch clause can never match a genuine timer-driven timeout in this overload.Environment
UtilitiesCS/Threading/TimeOutTask.csSteps to Reproduce
RunWithTimeout<T1, TResult>(theFunc<T1, TResult>overload declared atTimeOutTask.cs:164) with amillisecondsvalue short enough that the internalCancellationTokenSource-linkedTask.Runis cancelled by the timer before the delegate completes.System.Threading.Tasks.TaskCanceledException, notSystem.TimeoutException.catch (TimeoutException)clause at line 199 does not match, so the intended retry/timeout-handling branch never executes for a real timeout; the exception instead propagates unhandled to the caller.TaskCanceledExceptionfor the identicalCancellationTokenSource-driven pattern:RunWithTimeout<TResult>(Func<TResult>overload) at line 64,RunWithTimeout<TResult>(Func<CancellationToken,Task<TResult>>overload) at line 129,RunWithTimeout<T1,T2,TResult>at line 350,RunWithTimeout<T1,T2,T3,TResult>at line 580.Expected Behavior
A genuine timer-driven timeout in
RunWithTimeout<T1, TResult>should be caught and handled by the same retry ladder as every sibling overload (catch (TaskCanceledException)), somaxAttempts/strictretry behavior applies consistently across allRunWithTimeoutoverloads.Actual Behavior
catch (TimeoutException)at line 199 only matches aTimeoutExceptionthrown directly by the wrapped delegate itself (an unusual, non-default case). It never matches the actual timer-drivenTaskCanceledExceptionthatTask.Run(..., combinedToken)raises when the internalCancellationTokenSourcefires.TaskCanceledExceptionandTimeoutExceptionboth derive independently fromSystemExceptionand are unrelated types. As a result, the overload's own existing test coverage simulates "timeout" by having the wrapped delegate throwTimeoutExceptiondirectly, rather than via a genuinely shortmillisecondsvalue - masking the defect in the existing test suite.Logs / Screenshots
UtilitiesCS/Threading/TimeOutTask.cs:164(declaration) and:199(catch (TimeoutException)), compared against siblingcatch (TaskCanceledException)clauses at lines 64, 129, 350, 580. Originally identified and documented indocs/features/active/2026-07-07-onedrive-writer-timeout-test-determinism-253/research/2026-07-07T13-00-onedrive-writer-timeout-research.md(Section 2.1) anddocs/features/archive/2026-07-07-onedrive-writer-timeout-test-determinism-253/evidence/other/follow-up-issue-note.2026-07-07T14-05.md, both of which explicitly deferred this defect as out of scope for issue Bug: onedrive-writer-timeout-test-determinism #253 and recommended filing a new issue. No open GitHub issue currently referencesTimeOutTask(verified viagh issue list --search "TimeOutTask").Impact / Severity
The retry/timeout-handling contract for this one overload silently does not work for its primary intended case (a real timer-driven timeout); callers relying on
maxAttempts/strictretry-on-timeout behavior forRunWithTimeout<T1, TResult>get an unhandledTaskCanceledExceptioninstead of a retry.Source
From: docs/features/potential/2026-07-09-timeouttask-runwithtimeout-exception-type-mismatch.md