Skip to content
Draft
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ Increment the:

* [BUG] Keep the curl retry deadline stable within each attempt
[#4452](https://github.com/open-telemetry/opentelemetry-cpp/pull/4452)
* [BUG] Elasticsearch: report an asynchronous export's outcome exactly once
[#4502](https://github.com/open-telemetry/opentelemetry-cpp/pull/4502)
* [BUG] Stop the Elasticsearch async ForceFlush reporting success without
waiting for the sessions it was asked about
[#4337](https://github.com/open-telemetry/opentelemetry-cpp/pull/4337)

## [1.29.0] 2026-09-13

Expand Down
8 changes: 8 additions & 0 deletions exporters/elasticsearch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,12 @@ if(OTELCPP_BUILD_TESTING)
TARGET es_log_record_exporter_test
TEST_PREFIX exporter.
TEST_LIST es_log_record_exporter_test)

# AnIndefiniteFlushParksUntilTheOutcomeArrives really does park on the
# condition variable, so a regression there does not fail, it stalls until the
# exporter's own bound expires. CTest's default is 25 minutes, which is a long
# time to spend learning that. This is deliberately well above the bounds the
# cases use themselves, so that one of them expiring reports which case failed
# rather than being killed from the outside.
set_tests_properties(${es_log_record_exporter_test} PROPERTIES TIMEOUT 60)
endif() # OTELCPP_BUILD_TESTING
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

#ifdef ENABLE_ASYNC_EXPORT
# include <condition_variable>
# include <cstddef>
# include <cstdint>
# include <mutex>
# include <set>
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
Expand Down Expand Up @@ -122,8 +123,15 @@ class ElasticsearchLogRecordExporter final : public opentelemetry::sdk::logs::Lo

/**
* Force flush the exporter.
*
* Waits for the asynchronous sessions already started when the call was made, bounded by the
* caller's timeout rather than by the exporter's response timeout.
*
* @param timeout an option timeout, default to max.
* @return return true when all data are exported, and false when timeout
* @return true when each of those exports has reported a terminal outcome, false on timeout.
* The outcome is published before the session is torn down, so true does not mean
* FinishSession() has run, nor that the batch reached Elasticsearch, which is
* [#3075](https://github.com/open-telemetry/opentelemetry-cpp/issues/3075).
*/
bool ForceFlush(
std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override;
Expand All @@ -149,13 +157,21 @@ class ElasticsearchLogRecordExporter final : public opentelemetry::sdk::logs::Lo
#ifdef ENABLE_ASYNC_EXPORT
struct SynchronizationData
{
std::atomic<std::size_t> session_counter_{0};
std::atomic<std::size_t> finished_session_counter_{0};
// next_session_id and running_sessions are guarded by force_flush_cv_m, so a start or a
// finish cannot interleave with a waiter's snapshot. The wait compares ids by order, so they
// must keep increasing: uint64_t rather than size_t.
std::uint64_t next_session_id{0};
std::set<std::uint64_t> running_sessions;
std::condition_variable force_flush_cv;
std::mutex force_flush_cv_m;
std::recursive_mutex force_flush_m;

// Test synchronization. Raised under force_flush_cv_m once per ForceFlush(), immediately
// after that call takes its watermark. No exporter behaviour reads it.
std::uint64_t watermarks_taken{0};
};
nostd::shared_ptr<SynchronizationData> synchronization_data_;

friend class ElasticsearchExporterTestPeer;
#endif
};
} // namespace logs
Expand Down
Loading
Loading