Fix minor Scheduler data races.
Ensure that Scheduler::{is_failed_,has_been_shutdown_}
are properly guarded by a lock since they are accessed
from multiple threads concurrently.
In Scheduler::FailWithError(), ensure the PostTask()
call happens under the lock.
In particular, this can happen when several background worker
threads are posting tasks to the queue, and one of them calls
Scheduler::FailWithError() which will implicitly call PostQuit().
Change-Id: Ie0b70be0838b9fdcf4f41c3ae53bc5bd8e6d71d3
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/27080
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/scheduler.cc b/src/gn/scheduler.cc
index b40f29c..c6de66e 100644
--- a/src/gn/scheduler.cc
+++ b/src/gn/scheduler.cc
@@ -28,21 +28,29 @@
// Reentrancy check.
CHECK(!is_running_);
- // Ensure there is at least one task, (or else this will wait forever).
- if (is_failed_ || (work_count_.IsZero() && pool_work_count_.IsZero())) {
- // Flush any posted tasks (that were posted from the UI thread).
- main_thread_run_loop_->PostQuit();
- main_thread_run_loop_->Run();
- return !is_failed_;
- }
-
- has_been_shutdown_ = false;
- is_running_ = true;
- main_thread_run_loop_->Run();
+ // has_been_shutdown_ and is_failed_ are read or modified
+ // from other threads in Scheduler::FailWithError.
bool local_is_failed;
{
std::lock_guard<std::mutex> lock(lock_);
- local_is_failed = is_failed();
+ has_been_shutdown_ = false;
+ local_is_failed = is_failed_;
+ }
+
+ // Ensure there is at least one task, (or else this will wait forever).
+ if (local_is_failed || (work_count_.IsZero() && pool_work_count_.IsZero())) {
+ // Flush any posted tasks (that were posted from the UI thread).
+ main_thread_run_loop_->PostQuit();
+ main_thread_run_loop_->Run();
+ WaitForPoolTasks();
+ return !is_failed();
+ }
+
+ is_running_ = true;
+ main_thread_run_loop_->Run();
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ local_is_failed = is_failed_;
has_been_shutdown_ = true;
}
// Don't do this while holding |lock_|, since it will block on the workers,
@@ -63,10 +71,10 @@
if (is_failed_ || has_been_shutdown_)
return; // Ignore errors once we see one.
- is_failed_ = true;
- }
- task_runner()->PostTask([this, err]() { FailWithErrorOnMainThread(err); });
+ is_failed_ = true;
+ task_runner()->PostTask([this, err]() { FailWithErrorOnMainThread(err); });
+ }
}
void Scheduler::ScheduleWork(std::function<void()> work) {
diff --git a/src/gn/scheduler.h b/src/gn/scheduler.h
index 08c4eb6..0db3726 100644
--- a/src/gn/scheduler.h
+++ b/src/gn/scheduler.h
@@ -39,8 +39,10 @@
bool verbose_logging() const { return verbose_logging_; }
void set_verbose_logging(bool v) { verbose_logging_ = v; }
- // TODO(brettw) data race on this access (benign?).
- bool is_failed() const { return is_failed_; }
+ bool is_failed() const {
+ std::lock_guard<std::mutex> lock(lock_);
+ return is_failed_;
+ }
void Log(const std::string& verb, const std::string& msg);
void FailWithError(const Err& err);
diff --git a/src/util/msg_loop.cc b/src/util/msg_loop.cc
index 117daf8..ec88aab 100644
--- a/src/util/msg_loop.cc
+++ b/src/util/msg_loop.cc
@@ -38,8 +38,13 @@
return (!task_queue_.empty()) || should_quit_;
});
- if (should_quit_)
+ if (should_quit_) {
+ // Clear any remaninig items in the queue to avoid
+ // running them on the next Run() invocation.
+ while (!task_queue_.empty())
+ task_queue_.pop();
return;
+ }
task = std::move(task_queue_.front());
task_queue_.pop();