Skip to content

Bug: timeouttask-runwithtimeout-exception-type-mismatch #285

Description

@drmoisan
  • Work Mode: full-bug

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

  1. 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.
  2. Observe that the timer-driven cancellation raises System.Threading.Tasks.TaskCanceledException, not System.TimeoutException.
  3. 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.
  4. 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

  • Attached minimal logs or screenshot
  • Snippet: Confirmed directly against source at UtilitiesCS/Threading/TimeOutTask.cs:164 (declaration) and :199 (catch (TimeoutException)), compared against sibling catch (TaskCanceledException) clauses at lines 64, 129, 350, 580. Originally identified and documented in docs/features/active/2026-07-07-onedrive-writer-timeout-test-determinism-253/research/2026-07-07T13-00-onedrive-writer-timeout-research.md (Section 2.1) and docs/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 references TimeOutTask (verified via gh issue list --search "TimeOutTask").

Impact / Severity

  • Blocker
  • High
  • Medium
  • Low

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions