mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
fixed scheduled executor bug
This commit is contained in:
@@ -124,6 +124,15 @@ TEST_F(InstantOnLostManagerTest, ShutdownMultipleTimes) {
|
|||||||
EXPECT_FALSE(instant_on_lost_manager().Shutdown());
|
EXPECT_FALSE(instant_on_lost_manager().Shutdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(InstantOnLostManagerTest, ShutdownWhileOnLostAdvertising) {
|
||||||
|
instant_on_lost_manager().OnAdvertisingStarted(
|
||||||
|
std::string(kServiceIdA), ByteArray(kData1.data(), kData1.size()));
|
||||||
|
instant_on_lost_manager().OnAdvertisingStopped(std::string(kServiceIdA));
|
||||||
|
EXPECT_TRUE(instant_on_lost_manager().IsOnLostAdvertisingForTesting());
|
||||||
|
|
||||||
|
EXPECT_TRUE(instant_on_lost_manager().Shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(InstantOnLostManagerTest, AdvertisingOnShutdownManager) {
|
TEST_F(InstantOnLostManagerTest, AdvertisingOnShutdownManager) {
|
||||||
instant_on_lost_manager().Shutdown();
|
instant_on_lost_manager().Shutdown();
|
||||||
instant_on_lost_manager().OnAdvertisingStarted(
|
instant_on_lost_manager().OnAdvertisingStarted(
|
||||||
|
|||||||
@@ -14,8 +14,9 @@
|
|||||||
|
|
||||||
#include "internal/platform/implementation/linux/scheduled_executor.h"
|
#include "internal/platform/implementation/linux/scheduled_executor.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "absl/time/time.h"
|
#include "absl/time/time.h"
|
||||||
@@ -26,40 +27,80 @@ namespace linux {
|
|||||||
|
|
||||||
ScheduledExecutor::ScheduledExecutor()
|
ScheduledExecutor::ScheduledExecutor()
|
||||||
: executor_(std::make_unique<nearby::linux::Executor>()),
|
: executor_(std::make_unique<nearby::linux::Executor>()),
|
||||||
|
scheduler_state_(std::make_shared<SchedulerState>()),
|
||||||
|
scheduler_thread_([this]() { RunScheduler(); }),
|
||||||
shut_down_(false) {}
|
shut_down_(false) {}
|
||||||
|
|
||||||
|
ScheduledExecutor::~ScheduledExecutor() {
|
||||||
|
if (!shut_down_) {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScheduledExecutor::ScheduledTask::Cancel() {
|
||||||
|
State expected = State::kPending;
|
||||||
|
if (!state_.compare_exchange_strong(expected, State::kCancelled)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std::shared_ptr<SchedulerState> state = scheduler_state_.lock()) {
|
||||||
|
state->generation.fetch_add(1);
|
||||||
|
state->condition.notify_one();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScheduledExecutor::ScheduledTask::TryDispatch() {
|
||||||
|
State expected = State::kPending;
|
||||||
|
return state_.compare_exchange_strong(expected, State::kDispatched);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScheduledExecutor::ScheduledTask::IsCancelled() const {
|
||||||
|
return state_.load() == State::kCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScheduledExecutor::ScheduledTask::Run() {
|
||||||
|
if (task_ != nullptr) {
|
||||||
|
task_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cancelable is kept both in the executor context, and in the caller context.
|
// Cancelable is kept both in the executor context, and in the caller context.
|
||||||
// We want Cancelable to live until both caller and executor are done with it.
|
// We want Cancelable to live until both caller and executor are done with it.
|
||||||
// Exclusive ownership model does not work for this case;
|
// Exclusive ownership model does not work for this case;
|
||||||
// using std:shared_ptr<> instead of std::unique_ptr<>.
|
// using std:shared_ptr<> instead of std::unique_ptr<>.
|
||||||
std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
|
std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
|
||||||
Runnable &&runnable, absl::Duration duration) {
|
Runnable&& runnable, absl::Duration duration) {
|
||||||
if (shut_down_) {
|
if (shut_down_) {
|
||||||
LOG(ERROR) << __func__
|
LOG(ERROR) << __func__ << ": Attempt to Schedule on a shut down executor.";
|
||||||
<< ": Attempt to Schedule on a shut down executor.";
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleans completed tasks
|
auto deadline = std::chrono::steady_clock::now();
|
||||||
scheduled_tasks_.erase(
|
if (duration == absl::InfiniteDuration()) {
|
||||||
std::remove_if(
|
deadline = std::chrono::steady_clock::time_point::max();
|
||||||
scheduled_tasks_.begin(), scheduled_tasks_.end(),
|
} else if (duration > absl::ZeroDuration()) {
|
||||||
[](std::shared_ptr<ScheduledTask> &task) { return task->IsDone(); }),
|
deadline += absl::ToChronoNanoseconds(duration);
|
||||||
scheduled_tasks_.end());
|
}
|
||||||
|
|
||||||
std::shared_ptr<ScheduledTask> task =
|
std::shared_ptr<ScheduledTask> task;
|
||||||
std::make_shared<ScheduledTask>(std::move(runnable), duration);
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(scheduler_state_->mutex);
|
||||||
scheduled_tasks_.push_back(task);
|
if (scheduler_state_->shut_down) {
|
||||||
executor_->Execute([task]() { task->Start(); });
|
return nullptr;
|
||||||
|
}
|
||||||
|
task = std::make_shared<ScheduledTask>(std::move(runnable), deadline,
|
||||||
|
next_sequence_++, scheduler_state_);
|
||||||
|
scheduled_tasks_.push(task);
|
||||||
|
scheduler_state_->generation.fetch_add(1);
|
||||||
|
}
|
||||||
|
scheduler_state_->condition.notify_one();
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduledExecutor::Execute(Runnable &&runnable) {
|
void ScheduledExecutor::Execute(Runnable&& runnable) {
|
||||||
if (shut_down_) {
|
if (shut_down_) {
|
||||||
LOG(ERROR) << __func__
|
LOG(ERROR) << __func__ << ": Attempt to Execute on a shut down executor.";
|
||||||
<< ": Attempt to Execute on a shut down executor.";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,18 +108,71 @@ void ScheduledExecutor::Execute(Runnable &&runnable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScheduledExecutor::Shutdown() {
|
void ScheduledExecutor::Shutdown() {
|
||||||
if (!shut_down_) {
|
bool expected = false;
|
||||||
shut_down_ = true;
|
if (shut_down_.compare_exchange_strong(expected, true)) {
|
||||||
for (auto &task : scheduled_tasks_) {
|
{
|
||||||
task->Cancel();
|
std::lock_guard<std::mutex> lock(scheduler_state_->mutex);
|
||||||
|
scheduler_state_->shut_down = true;
|
||||||
|
while (!scheduled_tasks_.empty()) {
|
||||||
|
scheduled_tasks_.top()->Cancel();
|
||||||
|
scheduled_tasks_.pop();
|
||||||
|
}
|
||||||
|
scheduler_state_->generation.fetch_add(1);
|
||||||
|
}
|
||||||
|
scheduler_state_->condition.notify_all();
|
||||||
|
if (scheduler_thread_.joinable()) {
|
||||||
|
scheduler_thread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduled_tasks_.clear();
|
|
||||||
executor_->Shutdown();
|
executor_->Shutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG(ERROR) << __func__
|
LOG(ERROR) << __func__ << ": Attempt to Shutdown on a shut down executor.";
|
||||||
<< ": Attempt to Shutdown on a shut down executor.";
|
}
|
||||||
|
|
||||||
|
void ScheduledExecutor::RunScheduler() {
|
||||||
|
while (true) {
|
||||||
|
std::shared_ptr<ScheduledTask> task;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(scheduler_state_->mutex);
|
||||||
|
while (!scheduler_state_->shut_down) {
|
||||||
|
if (scheduled_tasks_.empty()) {
|
||||||
|
scheduler_state_->condition.wait(lock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
task = scheduled_tasks_.top();
|
||||||
|
if (task->IsCancelled()) {
|
||||||
|
scheduled_tasks_.pop();
|
||||||
|
task.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->deadline() > std::chrono::steady_clock::now()) {
|
||||||
|
uint64_t generation = scheduler_state_->generation.load();
|
||||||
|
scheduler_state_->condition.wait_until(
|
||||||
|
lock, task->deadline(), [this, generation]() {
|
||||||
|
return scheduler_state_->shut_down ||
|
||||||
|
scheduler_state_->generation.load() != generation;
|
||||||
|
});
|
||||||
|
task.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduled_tasks_.pop();
|
||||||
|
if (!task->TryDispatch()) {
|
||||||
|
task.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduler_state_->shut_down) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor_->Execute([task = std::move(task)]() { task->Run(); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace linux
|
} // namespace linux
|
||||||
} // namespace nearby
|
} // namespace nearby
|
||||||
|
|||||||
@@ -15,11 +15,17 @@
|
|||||||
#ifndef PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_
|
#ifndef PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_
|
||||||
#define PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_
|
#define PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "absl/synchronization/notification.h"
|
|
||||||
#include "absl/time/time.h"
|
#include "absl/time/time.h"
|
||||||
#include "internal/platform/implementation/cancelable.h"
|
#include "internal/platform/implementation/cancelable.h"
|
||||||
#include "internal/platform/implementation/linux/executor.h"
|
#include "internal/platform/implementation/linux/executor.h"
|
||||||
@@ -38,7 +44,7 @@ class ScheduledExecutor : public api::ScheduledExecutor {
|
|||||||
public:
|
public:
|
||||||
ScheduledExecutor();
|
ScheduledExecutor();
|
||||||
|
|
||||||
~ScheduledExecutor() override = default;
|
~ScheduledExecutor() override;
|
||||||
|
|
||||||
// Cancelable is kept both in the executor context, and in the caller context.
|
// Cancelable is kept both in the executor context, and in the caller context.
|
||||||
// We want Cancelable to live until both caller and executor are done with it.
|
// We want Cancelable to live until both caller and executor are done with it.
|
||||||
@@ -54,43 +60,62 @@ class ScheduledExecutor : public api::ScheduledExecutor {
|
|||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class ScheduledTask : public api::Cancelable {
|
struct SchedulerState {
|
||||||
public:
|
std::mutex mutex;
|
||||||
explicit ScheduledTask(Runnable&& task, absl::Duration duration)
|
std::condition_variable condition;
|
||||||
: task_(std::move(task)), duration_(duration) {}
|
std::atomic<uint64_t> generation = 0;
|
||||||
|
bool shut_down = false;
|
||||||
bool Cancel() override {
|
|
||||||
if (is_executed_ || is_cancelled_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_cancelled_ = true;
|
|
||||||
notification_.Notify();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Start() {
|
|
||||||
if (is_executed_ ||
|
|
||||||
notification_.WaitForNotificationWithTimeout(duration_)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_executed_ = true;
|
|
||||||
task_();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDone() const { return is_cancelled_ || is_executed_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Runnable task_;
|
|
||||||
absl::Duration duration_;
|
|
||||||
absl::Notification notification_;
|
|
||||||
bool is_cancelled_ = false;
|
|
||||||
bool is_executed_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ScheduledTask : public api::Cancelable {
|
||||||
|
public:
|
||||||
|
ScheduledTask(Runnable&& task,
|
||||||
|
std::chrono::steady_clock::time_point deadline,
|
||||||
|
uint64_t sequence,
|
||||||
|
std::weak_ptr<SchedulerState> scheduler_state)
|
||||||
|
: task_(std::move(task)),
|
||||||
|
deadline_(deadline),
|
||||||
|
sequence_(sequence),
|
||||||
|
scheduler_state_(std::move(scheduler_state)) {}
|
||||||
|
|
||||||
|
bool Cancel() override;
|
||||||
|
bool TryDispatch();
|
||||||
|
bool IsCancelled() const;
|
||||||
|
void Run();
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point deadline() const { return deadline_; }
|
||||||
|
uint64_t sequence() const { return sequence_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class State { kPending, kDispatched, kCancelled };
|
||||||
|
|
||||||
|
Runnable task_;
|
||||||
|
const std::chrono::steady_clock::time_point deadline_;
|
||||||
|
const uint64_t sequence_;
|
||||||
|
std::weak_ptr<SchedulerState> scheduler_state_;
|
||||||
|
std::atomic<State> state_{State::kPending};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScheduledTaskCompare {
|
||||||
|
bool operator()(const std::shared_ptr<ScheduledTask>& lhs,
|
||||||
|
const std::shared_ptr<ScheduledTask>& rhs) const {
|
||||||
|
if (lhs->deadline() == rhs->deadline()) {
|
||||||
|
return lhs->sequence() > rhs->sequence();
|
||||||
|
}
|
||||||
|
return lhs->deadline() > rhs->deadline();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RunScheduler();
|
||||||
|
|
||||||
std::unique_ptr<nearby::linux::Executor> executor_ = nullptr;
|
std::unique_ptr<nearby::linux::Executor> executor_ = nullptr;
|
||||||
std::vector<std::shared_ptr<ScheduledTask>> scheduled_tasks_;
|
std::shared_ptr<SchedulerState> scheduler_state_;
|
||||||
|
std::priority_queue<std::shared_ptr<ScheduledTask>,
|
||||||
|
std::vector<std::shared_ptr<ScheduledTask>>,
|
||||||
|
ScheduledTaskCompare>
|
||||||
|
scheduled_tasks_;
|
||||||
|
std::thread scheduler_thread_;
|
||||||
|
uint64_t next_sequence_ = 0;
|
||||||
std::atomic_bool shut_down_ = false;
|
std::atomic_bool shut_down_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -98,6 +99,36 @@ TEST(ScheduledExecutorTests, ScheduleSucceeds) {
|
|||||||
ASSERT_EQ(output, expected);
|
ASSERT_EQ(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ScheduledExecutorTests, DelayedTaskDoesNotBlockImmediateTask) {
|
||||||
|
absl::Notification notification;
|
||||||
|
ScheduledExecutor executor;
|
||||||
|
|
||||||
|
auto delayed = executor.Schedule([]() {}, absl::Seconds(2));
|
||||||
|
executor.Execute([¬ification]() { notification.Notify(); });
|
||||||
|
|
||||||
|
EXPECT_TRUE(
|
||||||
|
notification.WaitForNotificationWithTimeout(absl::Milliseconds(200)));
|
||||||
|
EXPECT_TRUE(delayed->Cancel());
|
||||||
|
executor.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScheduledExecutorTests, TasksRunInDeadlineOrder) {
|
||||||
|
absl::Notification short_task_ran;
|
||||||
|
std::atomic_bool long_task_ran = false;
|
||||||
|
ScheduledExecutor executor;
|
||||||
|
|
||||||
|
auto long_task = executor.Schedule(
|
||||||
|
[&long_task_ran]() { long_task_ran = true; }, absl::Seconds(2));
|
||||||
|
executor.Schedule([&short_task_ran]() { short_task_ran.Notify(); },
|
||||||
|
absl::Milliseconds(20));
|
||||||
|
|
||||||
|
EXPECT_TRUE(
|
||||||
|
short_task_ran.WaitForNotificationWithTimeout(absl::Milliseconds(200)));
|
||||||
|
EXPECT_FALSE(long_task_ran);
|
||||||
|
EXPECT_TRUE(long_task->Cancel());
|
||||||
|
executor.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ScheduledExecutorTests, CancelSucceeds) {
|
TEST(ScheduledExecutorTests, CancelSucceeds) {
|
||||||
absl::Notification notification;
|
absl::Notification notification;
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
Reference in New Issue
Block a user