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 
12 using base::Closure;
13 
14 namespace brillo {
15 
GlibMessageLoop()16 GlibMessageLoop::GlibMessageLoop() {
17   loop_ = g_main_loop_new(g_main_context_default(), FALSE);
18 }
19 
~GlibMessageLoop()20 GlibMessageLoop::~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 
PostDelayedTask(const tracked_objects::Location & from_here,const Closure & task,base::TimeDelta delay)31 MessageLoop::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 
WatchFileDescriptor(const tracked_objects::Location & from_here,int fd,WatchMode mode,bool persistent,const Closure & task)53 MessageLoop::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 
CancelTask(TaskId task_id)124 bool 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 
RunOnce(bool may_block)140 bool GlibMessageLoop::RunOnce(bool may_block) {
141   return g_main_context_iteration(nullptr, may_block);
142 }
143 
Run()144 void GlibMessageLoop::Run() {
145   g_main_loop_run(loop_);
146 }
147 
BreakLoop()148 void GlibMessageLoop::BreakLoop() {
149   g_main_loop_quit(loop_);
150 }
151 
NextTaskId()152 MessageLoop::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 
OnRanPostedTask(gpointer user_data)161 gboolean 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 
OnWatchedFdReady(GIOChannel * source,GIOCondition condition,gpointer user_data)173 gboolean 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 
DestroyPostedTask(gpointer user_data)190 void GlibMessageLoop::DestroyPostedTask(gpointer user_data) {
191   delete reinterpret_cast<ScheduledTask*>(user_data);
192 }
193 
194 }  // namespace brillo
195