blob: 20c271d6f842f60352849d51a8de4cba19af5eef [file] [log] [blame]
// 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/glib_message_loop.h>
#include <fcntl.h>
#include <unistd.h>
#include <brillo/location_logging.h>
using base::Closure;
namespace brillo {
GlibMessageLoop::GlibMessageLoop() {
loop_ = g_main_loop_new(g_main_context_default(), FALSE);
}
GlibMessageLoop::~GlibMessageLoop() {
// Cancel all pending tasks when destroying the message loop.
for (const auto& task : tasks_) {
DVLOG_LOC(task.second->location, 1)
<< "Removing task_id " << task.second->task_id
<< " leaked on GlibMessageLoop, scheduled from this location.";
g_source_remove(task.second->source_id);
}
g_main_loop_unref(loop_);
}
MessageLoop::TaskId GlibMessageLoop::PostDelayedTask(
const tracked_objects::Location& from_here,
const Closure &task,
base::TimeDelta delay) {
TaskId task_id = NextTaskId();
// Note: While we store persistent = false in the ScheduledTask object, we
// don't check it in OnRanPostedTask() since it is always false for delayed
// tasks. This is only used for WatchFileDescriptor below.
ScheduledTask* scheduled_task = new ScheduledTask{
this, from_here, task_id, 0, false, std::move(task)};
DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id
<< " to run in " << delay << ".";
scheduled_task->source_id = g_timeout_add_full(
G_PRIORITY_DEFAULT,
delay.InMillisecondsRoundedUp(),
&GlibMessageLoop::OnRanPostedTask,
reinterpret_cast<gpointer>(scheduled_task),
DestroyPostedTask);
tasks_[task_id] = scheduled_task;
return task_id;
}
MessageLoop::TaskId GlibMessageLoop::WatchFileDescriptor(
const tracked_objects::Location& from_here,
int fd,
WatchMode mode,
bool persistent,
const Closure &task) {
// Quick check to see if the fd is valid.
if (fcntl(fd, F_GETFD) == -1 && errno == EBADF)
return MessageLoop::kTaskIdNull;
GIOCondition condition = G_IO_NVAL;
switch (mode) {
case MessageLoop::kWatchRead:
condition = static_cast<GIOCondition>(G_IO_IN | G_IO_HUP | G_IO_NVAL);
break;
case MessageLoop::kWatchWrite:
condition = static_cast<GIOCondition>(G_IO_OUT | G_IO_HUP | G_IO_NVAL);
break;
default:
return MessageLoop::kTaskIdNull;
}
// TODO(deymo): Used g_unix_fd_add_full() instead of g_io_add_watch_full()
// when/if we switch to glib 2.36 or newer so we don't need to create a
// GIOChannel for this.
GIOChannel* io_channel = g_io_channel_unix_new(fd);
if (!io_channel)
return MessageLoop::kTaskIdNull;
GError* error = nullptr;
GIOStatus status = g_io_channel_set_encoding(io_channel, nullptr, &error);
if (status != G_IO_STATUS_NORMAL) {
LOG(ERROR) << "GError(" << error->code << "): "
<< (error->message ? error->message : "(unknown)");
g_error_free(error);
// g_io_channel_set_encoding() documentation states that this should be
// valid in this context (a new io_channel), but enforce the check in
// debug mode.
DCHECK(status == G_IO_STATUS_NORMAL);
return MessageLoop::kTaskIdNull;
}
TaskId task_id = NextTaskId();
ScheduledTask* scheduled_task = new ScheduledTask{
this, from_here, task_id, 0, persistent, std::move(task)};
scheduled_task->source_id = g_io_add_watch_full(
io_channel,
G_PRIORITY_DEFAULT,
condition,
&GlibMessageLoop::OnWatchedFdReady,
reinterpret_cast<gpointer>(scheduled_task),
DestroyPostedTask);
// g_io_add_watch_full() increases the reference count on the newly created
// io_channel, so we can dereference it now and it will be free'd once the
// source is removed or now if g_io_add_watch_full() failed.
g_io_channel_unref(io_channel);
DVLOG_LOC(from_here, 1)
<< "Watching fd " << fd << " for "
<< (mode == MessageLoop::kWatchRead ? "reading" : "writing")
<< (persistent ? " persistently" : " just once")
<< " as task_id " << task_id
<< (scheduled_task->source_id ? " successfully" : " failed.");
if (!scheduled_task->source_id) {
delete scheduled_task;
return MessageLoop::kTaskIdNull;
}
tasks_[task_id] = scheduled_task;
return task_id;
}
bool GlibMessageLoop::CancelTask(TaskId task_id) {
if (task_id == kTaskIdNull)
return false;
const auto task = tasks_.find(task_id);
// It is a programmer error to attempt to remove a non-existent source.
if (task == tasks_.end())
return false;
DVLOG_LOC(task->second->location, 1)
<< "Removing task_id " << task_id << " scheduled from this location.";
guint source_id = task->second->source_id;
// We remove here the entry from the tasks_ map, the pointer will be deleted
// by the g_source_remove() call.
tasks_.erase(task);
return g_source_remove(source_id);
}
bool GlibMessageLoop::RunOnce(bool may_block) {
return g_main_context_iteration(nullptr, may_block);
}
void GlibMessageLoop::Run() {
g_main_loop_run(loop_);
}
void GlibMessageLoop::BreakLoop() {
g_main_loop_quit(loop_);
}
MessageLoop::TaskId GlibMessageLoop::NextTaskId() {
TaskId res;
do {
res = ++last_id_;
// We would run out of memory before we run out of task ids.
} while (!res || tasks_.find(res) != tasks_.end());
return res;
}
gboolean GlibMessageLoop::OnRanPostedTask(gpointer user_data) {
ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
DVLOG_LOC(scheduled_task->location, 1)
<< "Running delayed task_id " << scheduled_task->task_id
<< " scheduled from this location.";
// We only need to remove this task_id from the map. DestroyPostedTask will be
// called with this same |user_data| where we can delete the ScheduledTask.
scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
scheduled_task->closure.Run();
return FALSE; // Removes the source since a callback can only be called once.
}
gboolean GlibMessageLoop::OnWatchedFdReady(GIOChannel *source,
GIOCondition condition,
gpointer user_data) {
ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
DVLOG_LOC(scheduled_task->location, 1)
<< "Running task_id " << scheduled_task->task_id
<< " for watching a file descriptor, scheduled from this location.";
if (!scheduled_task->persistent) {
// We only need to remove this task_id from the map. DestroyPostedTask will
// be called with this same |user_data| where we can delete the
// ScheduledTask.
scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
}
scheduled_task->closure.Run();
return scheduled_task->persistent;
}
void GlibMessageLoop::DestroyPostedTask(gpointer user_data) {
delete reinterpret_cast<ScheduledTask*>(user_data);
}
} // namespace brillo