{"record":{"id":"18d1eafdc57bf086","repo":"elsa-workflows/elsa-core","slug":"timeout","errorCode":"Timeout","errorMessage":"ProviderHttpException(ProviderHttpFailure.Timeout)","messagePattern":"ProviderHttpException\\(ProviderHttpFailure\\.Timeout\\)","errorType":"error_code","errorClass":"ProviderHttpException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/ProviderHttpClientFactory.cs","lineNumber":118,"sourceCode":"                        throw new ProviderHttpException(ProviderHttpFailure.RedirectRejected);\n\n                    current = new(current, response.Headers.Location);\n                    continue;\n                }\n\n                if (!response.IsSuccessStatusCode)\n                    return new(response.StatusCode, []);\n\n                return new(response.StatusCode, await ReadResponseBodyAsync(response, kind, timeout.Token));\n            }\n        }\n        catch (OutboundDestinationException)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.DestinationRejected);\n        }\n        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.Timeout);\n        }\n        catch (ProviderHttpException)\n        {\n            throw;\n        }\n        catch (Exception) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new ProviderHttpException(ProviderHttpFailure.TransportFailure);\n        }\n    }\n\n    private async Task<byte[]> ReadResponseBodyAsync(HttpResponseMessage response, ProviderResponseKind kind, CancellationToken cancellationToken)\n    {\n        var limit = GetResponseLimit(kind);\n        var contentLength = response.Content.Headers.ContentLength;\n        if (contentLength is not null && contentLength > limit)\n            throw new ProviderHttpException(ProviderHttpFailure.ResponseTooLarge);\n","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/ProviderHttpClientFactory.cs#L100-L136","documentation":"Each provider request runs under a linked CancellationTokenSource cancelled after ProviderEgress.RequestTimeout. If that internal timeout fires (the caller's own token is NOT cancelled — hence the when filter), SendAsync throws ProviderHttpException(ProviderHttpFailure.Timeout). It distinguishes the library's per-request timeout from caller-initiated cancellation, which propagates as OperationCanceledException instead.","triggerScenarios":"Calling GetAsync/PostFormAsync against a provider that takes longer than ProviderEgress.RequestTimeout to respond — slow token endpoint, hanging TLS handshake/connection (also bounded by ConnectTimeout), or a discovery endpoint stalled mid-response. Thrown only when the elapsed timeout triggered the cancellation and the caller's token is still live.","commonSituations":"Provider under load or rate-limiting delaying responses beyond the default timeout; network firewall silently dropping packets so the connection hangs until timeout; oversized/slow discovery or JWKS responses on high-latency links; misjudging the timeout budget when chaining token + userinfo calls.","solutions":["Increase ProviderEgress.RequestTimeout in ExternalAuthenticationOptions if the provider legitimately needs longer (e.g. 30s instead of the default).","Retry the request with backoff — timeouts are often transient; wrap the call in a Polly-style retry or your own loop.","Diagnose the provider/network: curl -w with timing against the endpoint to see if DNS, connect, TLS, or server response is the slow stage; check provider status pages.","Check ConnectTimeout as well — a stalled TCP connection is bounded by ConnectTimeout, so raising RequestTimeout alone may not help connection-level stalls."],"exampleFix":"// before\ncancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5)); // request timeout too tight\n\n// after — configured via options\nservices.AddOptions<ExternalAuthenticationOptions>()\n    .Configure(o => o.ProviderEgress.RequestTimeout = TimeSpan.FromSeconds(30));","handlingStrategy":"retry","validationCode":"// pre-flight reachability probe with explicit timing before using a provider endpoint\nusing var probe = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };\nvar sw = Stopwatch.StartNew();\nvar ok = (await probe.GetAsync(providerUrl)).IsSuccessStatusCode;\nif (!ok || sw.Elapsed > TimeSpan.FromSeconds(5))\n    logger.LogWarning(\"Provider endpoint slow or unreachable ({Elapsed}ms): {Url}\", sw.ElapsedMilliseconds, providerUrl);","typeGuard":"static bool IsEgressTimeout(ProviderHttpException ex) => ex.Failure == ProviderHttpFailure.Timeout;","tryCatchPattern":"for (var attempt = 1; attempt <= 3; attempt++)\n{\n    try\n    {\n        return await client.GetAsync(uri, kind, ct);\n    }\n    catch (ProviderHttpException ex) when (ex.Failure == ProviderHttpFailure.Timeout && attempt < 3)\n    {\n        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct); // exponential backoff\n    }\n}","preventionTips":["Size ProviderEgress.RequestTimeout above the provider's p99 latency; measure it rather than guessing.","Retry timeouts with exponential backoff — they are frequently transient, unlike DestinationRejected.","Check ConnectTimeout separately: a stalled connection is bounded by ConnectTimeout even if RequestTimeout is generous.","Alert on recurring Timeout failures per provider — persistent timeouts indicate a provider outage or network path problem, not a tuning issue."],"tags":["http","network","timeout","oauth"],"backgroundTag":"request-timeout","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}