// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <brillo/message_loops/fake_message_loop.h>
#include <base/logging.h>
#include <brillo/location_logging.h>
namespace brillo {
FakeMessageLoop::FakeMessageLoop(base::SimpleTestClock* clock)
: test_clock_(clock) {
}
MessageLoop::TaskId FakeMessageLoop::PostDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
// If no SimpleTestClock was provided, we use the last time we fired a
// callback. In this way, tasks scheduled from a Closure will have the right
// time.
if (test_clock_)
current_time_ = test_clock_->Now();
MessageLoop::TaskId current_id = ++last_id_;
// FakeMessageLoop is limited to only 2^64 tasks. That should be enough.
CHECK(current_id);
tasks_.emplace(current_id, ScheduledTask{from_here, false, task});
fire_order_.push(std::make_pair(current_time_ + delay, current_id));
VLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << current_id
<< " to run at " << current_time_ + delay
<< " (in " << delay << ").";
return current_id;
}
MessageLoop::TaskId FakeMessageLoop::WatchFileDescriptor(
const tracked_objects::Location& from_here,
int fd,
WatchMode mode,
bool persistent,
const base::Closure& task) {
MessageLoop::TaskId current_id = ++last_id_;
// FakeMessageLoop is limited to only 2^64 tasks. That should be enough.
CHECK(current_id);
tasks_.emplace(current_id, ScheduledTask{from_here, persistent, task});
fds_watched_.emplace(std::make_pair(fd, mode), current_id);
return current_id;
}
bool FakeMessageLoop::CancelTask(TaskId task_id) {
if (task_id == MessageLoop::kTaskIdNull)
return false;
bool ret = tasks_.erase(task_id) > 0;
VLOG_IF(1, ret) << "Removing task_id " << task_id;
return ret;
}
bool FakeMessageLoop::RunOnce(bool may_block) {
if (test_clock_)
current_time_ = test_clock_->Now();
// Try to fire ready file descriptors first.
for (const auto& fd_mode : fds_ready_) {
const auto& fd_watched = fds_watched_.find(fd_mode);
if (fd_watched == fds_watched_.end())
continue;
// The fd_watched->second task might have been canceled and we never removed
// it from the fds_watched_, so we fix that now.
const auto& scheduled_task_ref = tasks_.find(fd_watched->second);
if (scheduled_task_ref == tasks_.end()) {
fds_watched_.erase(fd_watched);
continue;
}
VLOG_LOC(scheduled_task_ref->second.location, 1)
<< "Running task_id " << fd_watched->second
<< " for watching file descriptor " << fd_mode.first << " for "
<< (fd_mode.second == MessageLoop::kWatchRead ? "reading" : "writing")
<< (scheduled_task_ref->second.persistent ?
" persistently" : " just once")
<< " scheduled from this location.";
if (scheduled_task_ref->second.persistent) {
scheduled_task_ref->second.callback.Run();
} else {
base::Closure callback = std::move(scheduled_task_ref->second.callback);
tasks_.erase(scheduled_task_ref);
fds_watched_.erase(fd_watched);
callback.Run();
}
return true;
}
// Try to fire time-based callbacks afterwards.
while (!fire_order_.empty() &&
(may_block || fire_order_.top().first <= current_time_)) {
const auto task_ref = fire_order_.top();
fire_order_.pop();
// We need to skip tasks in the priority_queue not in the |tasks_| map.
// This is normal if the task was canceled, as there is no efficient way
// to remove a task from the priority_queue.
const auto scheduled_task_ref = tasks_.find(task_ref.second);
if (scheduled_task_ref == tasks_.end())
continue;
// Advance the clock to the task firing time, if needed.
if (current_time_ < task_ref.first) {
current_time_ = task_ref.first;
if (test_clock_)
test_clock_->SetNow(current_time_);
}
// Move the Closure out of the map before delete it. We need to delete the
// entry from the map before we call the callback, since calling CancelTask
// for the task you are running now should fail and return false.
base::Closure callback = std::move(scheduled_task_ref->second.callback);
VLOG_LOC(scheduled_task_ref->second.location, 1)
<< "Running task_id " << task_ref.second
<< " at time " << current_time_ << " from this location.";
tasks_.erase(scheduled_task_ref);
callback.Run();
return true;
}
return false;
}
void FakeMessageLoop::SetFileDescriptorReadiness(int fd,
WatchMode mode,
bool ready) {
if (ready)
fds_ready_.emplace(fd, mode);
else
fds_ready_.erase(std::make_pair(fd, mode));
}
bool FakeMessageLoop::PendingTasks() {
for (const auto& task : tasks_) {
VLOG_LOC(task.second.location, 1)
<< "Pending " << (task.second.persistent ? "persistent " : "")
<< "task_id " << task.first << " scheduled from here.";
}
return !tasks_.empty();
}
} // namespace brillo