-
Notifications
You must be signed in to change notification settings - Fork 617
[BUG] Prevent lost condition-variable wakeups during OTLP file, periodic metric, and batch span processor shutdown #4365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e35bb01
6551f60
b28f264
255f810
c2e05e4
6b7a1db
18f3bd4
f39c117
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
| } | ||
| file_->background_thread_waker_cv.notify_all(); | ||
| std::unique_ptr<std::thread> background_flush_thread; | ||
| { | ||
|
|
@@ -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; | ||
|
|
@@ -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); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my understanding, When
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a preexisting issue, as previously
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the existing codes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no significant behavior change here because The new code does slightly expand the window for which a shutdown call can fail to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The behaviour before just follow the other OTLP exporters. Which mean when Shutdown is called, the pending records will be try to be exported once, but the new codes will break and skip |
||
| } | ||
|
|
||
| #ifdef ENABLE_THREAD_INSTRUMENTATION_PREVIEW | ||
|
|
@@ -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); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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_}; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fence is right, the wait predicate checks
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 One direction is still open though. This fixes the waker side ( |
||
| } | ||
| cv_.notify_all(); | ||
| worker_thread_.join(); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we apply this same guarded store plus notify in
OtlpFileSystemBackend::Shutdown()too? When there is nothing pending,ForceFlush()returns before notifying the worker, soShutdown()can return while the background thread remains parked untilflush_interval. The destructor now handles this correctly, but callers that keep the client alive afterShutdown()still retain the delayed worker.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.