Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ Increment the:
for process entity
[#4437](https://github.com/open-telemetry/opentelemetry-cpp/pull/4437)

* [BUG] Prevent lost condition-variable wakeups in OTLP file exporter and periodic
metric exporter
[#4365](https://github.com/open-telemetry/opentelemetry-cpp/pull/4365)

Important changes:

* [API] Never set a null global provider or propagator
Expand Down
31 changes: 24 additions & 7 deletions exporters/otlp/src/otlp_file_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,10 @@ class OPENTELEMETRY_LOCAL_SYMBOL OtlpFileSystemBackend : public OtlpFileAppender
{
if (file_)
{
{
std::lock_guard<std::mutex> waker_guard{file_->background_thread_waker_lock};
file_->is_shutdown.store(true, std::memory_order_release);
Comment thread
marcalff marked this conversation as resolved.
}
file_->background_thread_waker_cv.notify_all();
std::unique_ptr<std::thread> background_flush_thread;
{
Expand Down Expand Up @@ -1133,7 +1137,10 @@ class OPENTELEMETRY_LOCAL_SYMBOL OtlpFileSystemBackend : public OtlpFileAppender

bool Shutdown(std::chrono::microseconds timeout) noexcept override
{
file_->is_shutdown.store(true, std::memory_order_release);
{
std::lock_guard<std::mutex> waker_guard{file_->background_thread_waker_lock};
file_->is_shutdown.store(true, std::memory_order_release);
}

bool result = ForceFlush(timeout);
return result;
Expand Down Expand Up @@ -1482,21 +1489,26 @@ class OPENTELEMETRY_LOCAL_SYMBOL OtlpFileSystemBackend : public OtlpFileAppender
break;
}

if (concurrency_file->is_shutdown.load(std::memory_order_acquire))
{
break;
}

#ifdef ENABLE_THREAD_INSTRUMENTATION_PREVIEW
if (thread_instrumentation != nullptr)
{
thread_instrumentation->BeforeWait();
}
#endif /* ENABLE_THREAD_INSTRUMENTATION_PREVIEW */

bool is_shutdown = false;
{
std::unique_lock<std::mutex> lk(concurrency_file->background_thread_waker_lock);
concurrency_file->background_thread_waker_cv.wait_for(lk, flush_interval);
// Even though is_shutdown is atomic, the lock guarantees that either a change to
// is_shutdown will be observed, or background_thread_waker_cv will see the notification
// at shutdown. It is important to set is_shutdown prior to `wait_for` rather than
// as part of a condition in `wait_for` so that a shutdown while the thread is in
// `wait_for` will still call `std::fflush` below.
is_shutdown = concurrency_file->is_shutdown.load(std::memory_order_acquire);
if (!is_shutdown)
{
concurrency_file->background_thread_waker_cv.wait_for(lk, flush_interval);
}
Comment thread
marcalff marked this conversation as resolved.
}

#ifdef ENABLE_THREAD_INSTRUMENTATION_PREVIEW
Expand All @@ -1506,6 +1518,11 @@ class OPENTELEMETRY_LOCAL_SYMBOL OtlpFileSystemBackend : public OtlpFileAppender
}
#endif /* ENABLE_THREAD_INSTRUMENTATION_PREVIEW */

if (is_shutdown)
{
break;
}

{
std::size_t current_record_count =
concurrency_file->record_count.load(std::memory_order_acquire);
Expand Down
12 changes: 11 additions & 1 deletion sdk/src/metrics/export/periodic_exporting_metric_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ bool PeriodicExportingMetricReader::OnForceFlush(std::chrono::microseconds timeo
if (force_flush_pending_sequence_.load(std::memory_order_acquire) >
force_flush_notified_sequence_.load(std::memory_order_acquire))
{
is_force_wakeup_background_worker_.store(true, std::memory_order_release);
{
// Acquiring cv_m_ guarantees that the worker thread either is not currently waiting on cv_,
// or the notify below will cause it to re-check the wait condition.
std::lock_guard<std::mutex> cv_guard{cv_m_};
is_force_wakeup_background_worker_.store(true, std::memory_order_release);
}
cv_.notify_all();
}
return force_flush_notified_sequence_.load(std::memory_order_acquire) >= current_sequence;
Expand Down Expand Up @@ -283,6 +288,11 @@ bool PeriodicExportingMetricReader::OnShutDown(std::chrono::microseconds timeout
{
if (worker_thread_.joinable())
{
{
// Acquiring cv_m_ guarantees that the next time the worker thread checks the wait condition
// on cv_ (either from notify below or any other reason) it will see IsShutdown() return true.
std::lock_guard<std::mutex> cv_guard{cv_m_};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fence is right, the wait predicate checks IsShutdown() under cv_m_. the force flush path below has the same store-then-notify shape (the "must not wait for ever" workaround), in scope here or follow-up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's in the same vein, I've added the change to ForceFlush as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ForceFlush change looks right, and no lock-order problem with it: this path takes force_flush_m_ then cv_m_ inside the predicate, and the worker's cv_m_ lock is scoped to the do-while body so it is released before CollectAndExportOnce runs.

One direction is still open though. This fixes the waker side (cv_). The completion side is unchanged: CollectAndExportOnce CASes force_flush_notified_sequence_ and calls force_flush_cv_.notify_all() holding nothing, so an OnForceFlush caller can evaluate the predicate, miss the notify, then wait. That is what the "must not wait for ever" chunked wait is compensating for, so the chunked loop has to stay as long as that store-then-notify is unguarded. Same bucket as the wait_for race you described, fine as a follow-up, just noting why that workaround can't come out yet.

}
cv_.notify_all();
worker_thread_.join();
}
Expand Down
Loading