aboutsummaryrefslogtreecommitdiff
path: root/brillo/message_loops/fake_message_loop.cc
blob: 41f5b5184e56e9861d56c44f08f94d1f581d0802 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 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 base::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 base::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