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