dotnet/yarp · error · InvalidOperationException

An uninitialized, or 'default', ValueStopwatch cannot be…

Error message

An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.

What it means

ValueStopwatch is a struct that stores only a start timestamp; reading Elapsed on a default(ValueStopwatch) (start timestamp 0) is meaningless, so the getter throws InvalidOperationException. The zero check is the library's way of detecting an uninitialized instance, since StartNew is the only constructor and a real timestamp can never be 0. YARP uses this internally for cheap timing in hot paths.

Solutions

  1. Initialize the stopwatch eagerly with ValueStopwatch.StartNew() at declaration or in the constructor.
  2. Track a separate bool _started flag and guard the Elapsed read behind it.
  3. Use ValueStopwatch? (nullable) and check HasValue before reading Elapsed.
  4. If timing is optional, prefer nullable ValueStopwatch? over a sentinel default instance.

Example fix

// before
private ValueStopwatch _stopwatch;
public TimeSpan Elapsed => _stopwatch.Elapsed; // throws if never started

// after
private ValueStopwatch? _stopwatch;
public TimeSpan Elapsed => _stopwatch?.Elapsed ?? TimeSpan.Zero;
Defensive patterns

Strategy: validation

Validate before calling

ValueStopwatch? sw = null;
// only start when needed:
sw = ValueStopwatch.StartNew();
// read defensively:
var elapsed = sw?.Elapsed ?? TimeSpan.Zero;

Type guard

static bool IsRunning(ValueStopwatch sw) => sw != default;
// usage:
if (IsRunning(_stopwatch)) { var e = _stopwatch.Elapsed; }

Try / catch

try { var e = _stopwatch.Elapsed; }
catch (InvalidOperationException) { /* stopwatch not started; skip timing */ }

Prevention

When it happens

Trigger: Declaring a ValueStopwatch field, property, or local without assigning ValueStopwatch.StartNew(), then reading .Elapsed. For example a struct holding an optional timer where the timer is left default in some code path, or a field initialized only inside an if-branch that is later read unconditionally.

Common situations: A conditional StartNew() inside an if-block but an unconditional Elapsed read; refactoring a class stopwatch into a struct field and forgetting the initializer; default-constructing a parent struct that embeds a ValueStopwatch; sharing a stopwatch across async paths where one path never starts it.

Related errors


AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13). Data as JSON: /api/errors/be9932c4ca06e45f. Report an issue: GitHub.

Appendix: source

Thrown at src/ReverseProxy/Utilities/ValueStopwatch.cs:37

    private readonly long _startTimestamp;

    private ValueStopwatch(long startTimestamp)
    {
        _startTimestamp = startTimestamp;
    }

    /// <summary>
    /// Gets the time elapsed since the stopwatch was created with <see cref="StartNew"/>.
    /// </summary>
    public TimeSpan Elapsed
    {
        get
        {
            // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
            // So it being 0 is a clear indication of default(ValueStopwatch)
            if (_startTimestamp == 0)
            {
                throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
            }

            var end = Stopwatch.GetTimestamp();
            var timestampDelta = end - _startTimestamp;
            var ticks = (long)(_timestampToTicks * timestampDelta);
            return new TimeSpan(ticks);
        }
    }

    /// <summary>
    /// Creates a new <see cref="ValueStopwatch"/> that is ready to be used.
    /// </summary>
    public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
}

View on GitHub (pinned to bd11867bee)