1// Copyright 2015 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <brillo/message_loops/glib_message_loop.h>
6
7#include <fcntl.h>
8#include <unistd.h>
9
10#include <brillo/location_logging.h>
11
12using base::Closure;
13
14namespace brillo {
15
16GlibMessageLoop::GlibMessageLoop() {
17  loop_ = g_main_loop_new(g_main_context_default(), FALSE);
18}
19
20GlibMessageLoop::~GlibMessageLoop() {
21  // Cancel all pending tasks when destroying the message loop.
22  for (const auto& task : tasks_) {
23    DVLOG_LOC(task.second->location, 1)
24        << "Removing task_id " << task.second->task_id
25        << " leaked on GlibMessageLoop, scheduled from this location.";
26    g_source_remove(task.second->source_id);
27  }
28  g_main_loop_unref(loop_);
29}
30
31MessageLoop::TaskId GlibMessageLoop::PostDelayedTask(
32    const tracked_objects::Location& from_here,
33    const Closure &task,
34    base::TimeDelta delay) {
35  TaskId task_id =  NextTaskId();
36  // Note: While we store persistent = false in the ScheduledTask object, we
37  // don't check it in OnRanPostedTask() since it is always false for delayed
38  // tasks. This is only used for WatchFileDescriptor below.
39  ScheduledTask* scheduled_task = new ScheduledTask{
40    this, from_here, task_id, 0, false, std::move(task)};
41  DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id
42                          << " to run in " << delay << ".";
43  scheduled_task->source_id = g_timeout_add_full(
44      G_PRIORITY_DEFAULT,
45      delay.InMillisecondsRoundedUp(),
46      &GlibMessageLoop::OnRanPostedTask,
47      reinterpret_cast<gpointer>(scheduled_task),
48      DestroyPostedTask);
49  tasks_[task_id] = scheduled_task;
50  return task_id;
51}
52
53MessageLoop::TaskId GlibMessageLoop::WatchFileDescriptor(
54    const tracked_objects::Location& from_here,
55    int fd,
56    WatchMode mode,
57    bool persistent,
58    const Closure &task) {
59  // Quick check to see if the fd is valid.
60  if (fcntl(fd, F_GETFD) == -1 && errno == EBADF)
61      return MessageLoop::kTaskIdNull;
62
63  GIOCondition condition = G_IO_NVAL;
64  switch (mode) {
65    case MessageLoop::kWatchRead:
66      condition = static_cast<GIOCondition>(G_IO_IN | G_IO_HUP | G_IO_NVAL);
67      break;
68    case MessageLoop::kWatchWrite:
69      condition = static_cast<GIOCondition>(G_IO_OUT | G_IO_HUP | G_IO_NVAL);
70      break;
71    default:
72      return MessageLoop::kTaskIdNull;
73  }
74
75  // TODO(deymo): Used g_unix_fd_add_full() instead of g_io_add_watch_full()
76  // when/if we switch to glib 2.36 or newer so we don't need to create a
77  // GIOChannel for this.
78  GIOChannel* io_channel = g_io_channel_unix_new(fd);
79  if (!io_channel)
80    return MessageLoop::kTaskIdNull;
81  GError* error = nullptr;
82  GIOStatus status = g_io_channel_set_encoding(io_channel, nullptr, &error);
83  if (status != G_IO_STATUS_NORMAL) {
84    LOG(ERROR) << "GError(" << error->code << "): "
85               << (error->message ? error->message : "(unknown)");
86    g_error_free(error);
87    // g_io_channel_set_encoding() documentation states that this should be
88    // valid in this context (a new io_channel), but enforce the check in
89    // debug mode.
90    DCHECK(status == G_IO_STATUS_NORMAL);
91    return MessageLoop::kTaskIdNull;
92  }
93
94  TaskId task_id =  NextTaskId();
95  ScheduledTask* scheduled_task = new ScheduledTask{
96    this, from_here, task_id, 0, persistent, std::move(task)};
97  scheduled_task->source_id = g_io_add_watch_full(
98      io_channel,
99      G_PRIORITY_DEFAULT,
100      condition,
101      &GlibMessageLoop::OnWatchedFdReady,
102      reinterpret_cast<gpointer>(scheduled_task),
103      DestroyPostedTask);
104  // g_io_add_watch_full() increases the reference count on the newly created
105  // io_channel, so we can dereference it now and it will be free'd once the
106  // source is removed or now if g_io_add_watch_full() failed.
107  g_io_channel_unref(io_channel);
108
109  DVLOG_LOC(from_here, 1)
110      << "Watching fd " << fd << " for "
111      << (mode == MessageLoop::kWatchRead ? "reading" : "writing")
112      << (persistent ? " persistently" : " just once")
113      << " as task_id " << task_id
114      << (scheduled_task->source_id ? " successfully" : " failed.");
115
116  if (!scheduled_task->source_id) {
117    delete scheduled_task;
118    return MessageLoop::kTaskIdNull;
119  }
120  tasks_[task_id] = scheduled_task;
121  return task_id;
122}
123
124bool GlibMessageLoop::CancelTask(TaskId task_id) {
125  if (task_id == kTaskIdNull)
126    return false;
127  const auto task = tasks_.find(task_id);
128  // It is a programmer error to attempt to remove a non-existent source.
129  if (task == tasks_.end())
130    return false;
131  DVLOG_LOC(task->second->location, 1)
132      << "Removing task_id " << task_id << " scheduled from this location.";
133  guint source_id = task->second->source_id;
134  // We remove here the entry from the tasks_ map, the pointer will be deleted
135  // by the g_source_remove() call.
136  tasks_.erase(task);
137  return g_source_remove(source_id);
138}
139
140bool GlibMessageLoop::RunOnce(bool may_block) {
141  return g_main_context_iteration(nullptr, may_block);
142}
143
144void GlibMessageLoop::Run() {
145  g_main_loop_run(loop_);
146}
147
148void GlibMessageLoop::BreakLoop() {
149  g_main_loop_quit(loop_);
150}
151
152MessageLoop::TaskId GlibMessageLoop::NextTaskId() {
153  TaskId res;
154  do {
155    res = ++last_id_;
156    // We would run out of memory before we run out of task ids.
157  } while (!res || tasks_.find(res) != tasks_.end());
158  return res;
159}
160
161gboolean GlibMessageLoop::OnRanPostedTask(gpointer user_data) {
162  ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
163  DVLOG_LOC(scheduled_task->location, 1)
164      << "Running delayed task_id " << scheduled_task->task_id
165      << " scheduled from this location.";
166  // We only need to remove this task_id from the map. DestroyPostedTask will be
167  // called with this same |user_data| where we can delete the ScheduledTask.
168  scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
169  scheduled_task->closure.Run();
170  return FALSE;  // Removes the source since a callback can only be called once.
171}
172
173gboolean GlibMessageLoop::OnWatchedFdReady(GIOChannel *source,
174                                           GIOCondition condition,
175                                           gpointer user_data) {
176  ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
177  DVLOG_LOC(scheduled_task->location, 1)
178      << "Running task_id " << scheduled_task->task_id
179      << " for watching a file descriptor, scheduled from this location.";
180  if (!scheduled_task->persistent) {
181    // We only need to remove this task_id from the map. DestroyPostedTask will
182    // be called with this same |user_data| where we can delete the
183    // ScheduledTask.
184    scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
185  }
186  scheduled_task->closure.Run();
187  return scheduled_task->persistent;
188}
189
190void GlibMessageLoop::DestroyPostedTask(gpointer user_data) {
191  delete reinterpret_cast<ScheduledTask*>(user_data);
192}
193
194}  // namespace brillo
195