blob: d72b99d8ad38e0cd09310a9a5043e12cd0958901 [file] [log] [blame] [edit]
// Copyright 2019 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 "glib-bridge/glib_bridge.h"
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/threading/sequenced_task_runner_handle.h>
namespace glib_bridge {
namespace {
struct GMainContextLock {
public:
explicit GMainContextLock(GMainContext* context) : context_(context) {
CHECK(context_);
CHECK(g_main_context_acquire(context_));
}
~GMainContextLock() { g_main_context_release(context_); }
private:
GMainContext* context_; // weak
};
} // namespace
GlibBridge::GlibBridge()
: glib_context_(g_main_context_new()),
state_(State::kPreparingIteration),
weak_ptr_factory_(this) {
CHECK(glib_context_);
g_main_context_push_thread_default(glib_context_);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&GlibBridge::PrepareIteration,
weak_ptr_factory_.GetWeakPtr()));
}
GlibBridge::~GlibBridge() {
g_main_context_pop_thread_default(glib_context_);
g_main_context_unref(glib_context_);
}
void GlibBridge::PrepareIteration() {
CHECK_EQ(state_, State::kPreparingIteration);
CHECK(watchers_.empty());
GMainContextLock _l(glib_context_);
bool immediate = g_main_context_prepare(glib_context_, &max_priority_);
int num_fds =
g_main_context_query(glib_context_, max_priority_, nullptr, nullptr, 0);
poll_fds_ = std::vector<GPollFD>(num_fds);
int timeout_ms;
g_main_context_query(glib_context_, max_priority_, &timeout_ms, &poll_fds_[0],
num_fds);
if (immediate || (num_fds == 0 && timeout_ms == 0)) {
DVLOG(1) << "Iteration can be dispatched immediately";
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&GlibBridge::Dispatch, weak_ptr_factory_.GetWeakPtr()));
state_ = State::kReadyForDispatch;
return;
}
// Collect information about which poll flags we need for each fd.
std::map<int, int> poll_flags;
for (GPollFD& poll_fd : poll_fds_) {
fd_map_[poll_fd.fd].push_back(&poll_fd);
poll_flags[poll_fd.fd] |= poll_fd.events;
}
DVLOG(1) << "Preparing iteration with timeout " << timeout_ms << " ms, "
<< poll_flags.size() << " event FDs";
for (const auto& fd_flags : poll_flags) {
std::unique_ptr<base::FileDescriptorWatcher::Controller> reader;
if (fd_flags.second & G_IO_IN) {
reader = base::FileDescriptorWatcher::WatchReadable(
fd_flags.first, base::BindRepeating(&GlibBridge::OnEvent,
weak_ptr_factory_.GetWeakPtr(),
fd_flags.first, G_IO_IN));
CHECK(reader) << "Could not set up read watcher for fd "
<< fd_flags.first;
}
std::unique_ptr<base::FileDescriptorWatcher::Controller> writer;
if (fd_flags.second & G_IO_OUT) {
writer = base::FileDescriptorWatcher::WatchWritable(
fd_flags.first, base::BindRepeating(&GlibBridge::OnEvent,
weak_ptr_factory_.GetWeakPtr(),
fd_flags.first, G_IO_OUT));
CHECK(writer) << "Could not set up write watcher for fd "
<< fd_flags.first;
}
watchers_[fd_flags.first] = Watcher{std::move(reader), std::move(writer)};
}
state_ = State::kWaitingForEvents;
if (timeout_ms < 0)
return;
base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(timeout_ms);
timeout_closure_.Reset(
base::BindOnce(&GlibBridge::Timeout, weak_ptr_factory_.GetWeakPtr()));
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, timeout_closure_.callback(), timeout);
}
void GlibBridge::OnEvent(int fd, int flag) {
CHECK(state_ == State::kWaitingForEvents ||
state_ == State::kReadyForDispatch);
DVLOG(2) << "OnEvent(" << fd << ", " << flag << ")";
for (GPollFD* poll_fd : fd_map_[fd])
poll_fd->revents |= flag & poll_fd->events;
if (flag & G_IO_IN)
watchers_[fd].reader.reset();
if (flag & G_IO_OUT)
watchers_[fd].writer.reset();
// Avoid posting the dispatch task if it's already posted
if (state_ == State::kReadyForDispatch)
return;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&GlibBridge::Dispatch, weak_ptr_factory_.GetWeakPtr()));
state_ = State::kReadyForDispatch;
}
void GlibBridge::Timeout() {
CHECK_EQ(state_, State::kWaitingForEvents);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&GlibBridge::Dispatch, weak_ptr_factory_.GetWeakPtr()));
state_ = State::kReadyForDispatch;
}
void GlibBridge::Dispatch() {
CHECK_EQ(state_, State::kReadyForDispatch);
GMainContextLock _l(glib_context_);
bool dispatched = g_main_context_check(glib_context_, max_priority_,
poll_fds_.data(), poll_fds_.size());
g_main_context_dispatch(glib_context_);
DVLOG(2) << (dispatched ? "Found" : "Did not find") << " source to dispatch";
timeout_closure_.Cancel();
watchers_.clear();
poll_fds_.clear();
fd_map_.clear();
max_priority_ = -1;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&GlibBridge::PrepareIteration,
weak_ptr_factory_.GetWeakPtr()));
state_ = State::kPreparingIteration;
}
} // namespace glib_bridge