A timeout marks an expired limit. Identify where the operation waited before choosing a fix.
Locate the expired limit
Keep the exception type, message, inner exception, relevant application stack, timestamp, and request correlation ID. Record the runtime, affected operation, recent changes, and whether the failure repeats. Sanitize the evidence before sharing it with an AI assistant.
Distinguish a request deadline, database command timeout, connection-open timeout, and caller cancellation. They fail at different boundaries. Extending a limit may be a temporary operational decision, but it does not identify the cause.
Choose a check that separates the causes
- A request spends most of its time inside a database command: inspect SQL duration and waits. If lock waits dominate, capture the blocking chain and the head blocker’s transaction before proposing an index.
- ThreadPool thread count climbs while requests slow and CPU is not saturated: inspect worker stacks for blocking. Queue length can help, but a zero queue does not rule starvation out.
- CPU rises with the slow operation: collect a CPU profile during that workload and inspect the hot call paths. Low CPU, by itself, does not establish starvation.
- The delay occurs before a command starts: investigate connection acquisition and dependency timing. A query plan cannot account for time spent waiting outside query execution.
Capture the slow interval
These are illustrative commands, not diagnostics executed for this note. They assume compatible installed tools and an authorized modern .NET process; they are not a .NET Framework recipe. Select the correct process and collect while the symptom occurs.
Use a second terminal for the stack snapshot while counters run. For an intermittent problem, collect a bounded trace across the failure instead: one snapshot may miss it. Counter names vary by runtime; the newer System.Runtime metrics are available from .NET 9. Interpret cumulative CPU-time counters as change over an interval, not as a CPU percentage.
dotnet-counters ps dotnet-counters monitor --process-id <PID> --counters System.Runtime dotnet-stack report --process-id <PID>
Record a decision, then test it
For each hypothesis, record the observation, an alternative explanation, and the next discriminating check. For example: if repeated slow-interval stacks show an application method waiting on Task.Result, inspect that call path and test an async-throughout change. The stack supports blocking at that point; request timing still has to establish its effect on the customer operation.
Ask AI to review that evidence and identify gaps. After a change, repeat the same workload and compare latency, completed work, failures, and the diagnostic signal that motivated the fix. Retain a rejected hypothesis when the measurements disprove it.