Skip to content

[BUG] SessionState::TimedOut is never dispatched, so every timeout surfaces as SendFailed #4604

Open

Description

@mateenali66

Describe your environment

main at d64b2512, macOS 26.6.2, Apple clang, CMake Debug build with WITH_ELASTICSEARCH=ON, libcurl 8.7.1. The curl client is the only HTTP client in tree.

Steps to reproduce

Point the Elasticsearch log exporter at a socket that accepts the connection and never answers, with a 1 second response timeout.

ElasticsearchExporterOptions options("127.0.0.1", port, "logs", /*response_timeout=*/1);
ElasticsearchLogRecordExporter exporter(options);
auto record = exporter.MakeRecordable();
record->SetBody("probe");
exporter.Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
s = socket.socket(); s.bind(("127.0.0.1", port)); s.listen(8)
while True: c, _ = s.accept()   # accept, read nothing, answer nothing

What is the expected behavior?

SetTimeoutMs() expires, the session reports SessionState::TimedOut, and the exporter logs [ES Log Exporter] Request timed out from the handler at es_log_record_exporter.cc:178. The doc comment at :114 states this directly: the longest wait is "the timeout of the request, set by SetTimeoutMs(), which arrives here as a TimedOut session event".

What is the actual behavior?

SendFailed, every time. Two runs:

log +     0.4ms  [ES Log Exporter] Sending request
log +  1006.4ms  [ES Log Exporter] Failed to send request
export 0 result=FAILURE elapsed=1006.4ms
log +  1002.9ms  [ES Log Exporter] Failed to send request
export 1 result=FAILURE elapsed=1003.0ms

The timeout is honoured, so this is a classification problem rather than a hang.

Additional context

SessionState::TimedOut is declared at ext/include/opentelemetry/ext/http/client/http_client.h:93 and is never dispatched anywhere under ext/. HttpOperation::PerformCurlMessage (http_operation_curl.cc:1564-1594) maps every non-CURLE_OK code to ConnectFailed when the state is Connecting and to SendFailed when it is Connected or Sending, so CURLE_OPERATION_TIMEDOUT is indistinguishable from a refused connection or a dropped socket.

Four handler branches wait for an event that cannot arrive: es_log_record_exporter.cc:178 and :320, otlp_http_client.cc:200 and :303. A caller cannot currently tell a timeout from a transport failure, which matters because the two usually deserve different retry behaviour.

Two ways out, and I do not want to guess which you prefer. Dispatch TimedOut for CURLE_OPERATION_TIMEDOUT in PerformCurlMessage, which makes the four branches live and could change behaviour for anyone switching on SendFailed today. Or drop the enum value with its four branches and correct the comment at :114. Happy to send a PR either way.

Activity

  1. added
    needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.
    on Sep 19, 2026
  2. om7057 commented on Sep 28, 2026

    @om7057
    Contributor

    I went ahead. Opened #4647 taking the drop-the-enum direction, since it doesn't change behavior for anyone already handling a timeout via SendFailed. Open to rework it to the dispatch-TimedOut direction instead if you'd rather have that, or if you'd already started on this let me know!

  3. mateenali66 commented on Sep 28, 2026

    @mateenali66
    MemberAuthor

    I hadn't started, so no clash.

  4. added 2 commits that reference this issue on Sep 29, 2026
    f8eb29a
    b3151b9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions