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