summaryrefslogtreecommitdiffstats
path: root/base/message_pump_glib.cc
diff options
context:
space:
mode:
authordsh@google.com <dsh@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-03 16:52:59 +0000
committerdsh@google.com <dsh@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-03 16:52:59 +0000
commit8fc3a4815737707de36239119a3d3c648b826e2f (patch)
tree212caac6e386552d01794b68fbae74722cea456f /base/message_pump_glib.cc
parent748f1130a5fc046b5042390dd08b27df4b64c0cc (diff)
downloadchromium_src-8fc3a4815737707de36239119a3d3c648b826e2f.zip
chromium_src-8fc3a4815737707de36239119a3d3c648b826e2f.tar.gz
chromium_src-8fc3a4815737707de36239119a3d3c648b826e2f.tar.bz2
Implement MessageLoopForUI using GLib. This gets some exercise from
base_unittest. BUG=1319 Review URL: http://codereview.chromium.org/4261 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2834 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/message_pump_glib.cc')
-rw-r--r--base/message_pump_glib.cc304
1 files changed, 304 insertions, 0 deletions
diff --git a/base/message_pump_glib.cc b/base/message_pump_glib.cc
new file mode 100644
index 0000000..603c80ea
--- /dev/null
+++ b/base/message_pump_glib.cc
@@ -0,0 +1,304 @@
+// Copyright (c) 2008 The Chromium 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 "base/message_pump_glib.h"
+
+#include <fcntl.h>
+#include <math.h>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/platform_thread.h"
+
+namespace base {
+
+static const char kWorkScheduled = '\0';
+static const char kDelayedWorkScheduled = '\1';
+
+// I wish these could be const, but g_source_new wants a non-const GSourceFunc
+// pointer.
+
+// static
+GSourceFuncs MessagePumpForUI::WorkSourceFuncs = {
+ WorkSourcePrepare,
+ WorkSourceCheck,
+ WorkSourceDispatch,
+ NULL
+};
+
+// static
+GSourceFuncs MessagePumpForUI::IdleSourceFuncs = {
+ IdleSourcePrepare,
+ IdleSourceCheck,
+ IdleSourceDispatch,
+ NULL
+};
+
+static int GetTimeIntervalMilliseconds(Time from) {
+ if (from.is_null())
+ return -1;
+
+ // Be careful here. TimeDelta has a precision of microseconds, but we want a
+ // value in milliseconds. If there are 5.5ms left, should the delay be 5 or
+ // 6? It should be 6 to avoid executing delayed work too early.
+ double timeout = ceil((from - Time::Now()).InMillisecondsF());
+
+ // If this value is negative, then we need to run delayed work soon.
+ int delay = static_cast<int>(timeout);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
+MessagePumpForUI::MessagePumpForUI()
+ : state_(NULL),
+ context_(g_main_context_default()) {
+ // Create a pipe with a non-blocking read end for use by ScheduleWork to
+ // break us out of a poll. Create the work source and attach the file
+ // descriptor to it.
+ int pipe_fd[2];
+ CHECK(0 == pipe(pipe_fd)) << "Could not create pipe!";
+ write_fd_work_scheduled_ = pipe_fd[1];
+ read_fd_work_scheduled_ = pipe_fd[0];
+ int flags = fcntl(read_fd_work_scheduled_, F_GETFL, 0);
+ if (-1 == flags)
+ flags = 0;
+ CHECK(0 == fcntl(read_fd_work_scheduled_, F_SETFL, flags | O_NONBLOCK)) <<
+ "Could not set file descriptor to non-blocking!";
+ GPollFD poll_fd;
+ poll_fd.fd = read_fd_work_scheduled_;
+ poll_fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
+ work_source_ = AddSource(&WorkSourceFuncs, G_PRIORITY_DEFAULT, &poll_fd);
+}
+
+MessagePumpForUI::~MessagePumpForUI() {
+ close(read_fd_work_scheduled_);
+ close(write_fd_work_scheduled_);
+ g_source_destroy(work_source_);
+ g_source_unref(work_source_);
+}
+
+struct ThreadIdTraits {
+ static void New(void* instance) {
+ int* thread_id = static_cast<int*>(instance);
+ *thread_id = PlatformThread::CurrentId();
+ }
+ static void Delete(void* instance) {
+ }
+};
+
+void MessagePumpForUI::Run(Delegate* delegate) {
+ // Make sure we only run this on one thread. GTK only has one message pump
+ // so we can only have one UI loop per process.
+ static LazyInstance<int, ThreadIdTraits> thread_id(base::LINKER_INITIALIZED);
+ DCHECK(thread_id.Get() == PlatformThread::CurrentId()) <<
+ "Running MessagePumpForUI on two different threads; "
+ "this is unsupported by GLib!";
+
+ RunState state;
+ state.delegate = delegate;
+ state.keep_running = true;
+ // We emulate the behavior of MessagePumpDefault and try to do work at once.
+ state.should_do_work = true;
+ state.should_do_idle_work = false;
+ state.idle_source = NULL;
+
+ RunState* previous_state = state_;
+ state_ = &state;
+
+ while (state.keep_running)
+ g_main_context_iteration(context_, true);
+
+ if (state.idle_source) {
+ // This removes the source from the context and releases GLib's hold on it.
+ g_source_destroy(state.idle_source);
+ // This releases our hold and destroys the source.
+ g_source_unref(state.idle_source);
+ }
+
+ state_ = previous_state;
+}
+
+void MessagePumpForUI::Quit() {
+ DCHECK(state_) << "Quit called outside Run!";
+ state_->keep_running = false;
+}
+
+void MessagePumpForUI::ScheduleWork() {
+ // This can be called on any thread, so we don't want to touch any state
+ // variables as we would then need locks all over. This ensures that if
+ // we are sleeping in a poll that we will wake up, and we check the pipe
+ // so we know when work was scheduled.
+ CHECK(1 == write(write_fd_work_scheduled_, &kWorkScheduled, 1)) <<
+ "Could not write to pipe!";
+}
+
+void MessagePumpForUI::ScheduleDelayedWork(const Time& delayed_work_time) {
+ delayed_work_time_ = delayed_work_time;
+ // This is an optimization. Delayed work may not be imminent, we may just
+ // need to update our timeout to poll. Hence we don't want to go overkill
+ // with kWorkScheduled.
+ CHECK(1 == write(write_fd_work_scheduled_, &kDelayedWorkScheduled, 1)) <<
+ "Could not write to pipe!";
+}
+
+// A brief refresher on GLib:
+// GLib sources have four callbacks: Prepare, Check, Dispatch and Finalize.
+// On each iteration of the GLib pump, it calls each source's Prepare function.
+// This function should return TRUE if it wants GLib to call its Dispatch, and
+// FALSE otherwise. It can also set a timeout in this case for the next time
+// Prepare should be called again (it may be called sooner).
+// After the Prepare calls, GLib does a poll to check for events from the
+// system. File descriptors can be attached to the sources. The poll may block
+// if none of the Prepare calls returned TRUE. It will block indefinitely, or
+// by the minimum time returned by a source in Prepare.
+// After the poll, GLib calls Check for each source that returned FALSE
+// from Prepare. The return value of Check has the same meaning as for Prepare,
+// making Check a second chance to tell GLib we are ready for Dispatch.
+// Finally, GLib calls Dispatch for each source that is ready. If Dispatch
+// returns FALSE, GLib will destroy the source. Dispatch calls may be recursive
+// (i.e., you can call Run from them), but Prepare and Check cannot.
+// Finalize is called when the source is destroyed.
+
+// static
+gboolean MessagePumpForUI::WorkSourcePrepare(GSource* source,
+ gint* timeout_ms) {
+ MessagePumpForUI* self = static_cast<WorkSource*>(source)->self;
+ RunState* state = self->state_;
+
+ if (state->should_do_work) {
+ state->should_do_idle_work = false;
+ *timeout_ms = 0;
+ return TRUE;
+ }
+
+ *timeout_ms = GetTimeIntervalMilliseconds(self->delayed_work_time_);
+
+ state->should_do_idle_work = true;
+ // We want to do idle work right before poll goes to sleep. Obviously
+ // we are not currently asleep, but we may be about to since we have
+ // no work to do. If we don't have an idle source ready to go it's
+ // probably because it fired already (or we just started and it hasn't
+ // been added yet) and we should add one for when we are ready. Note
+ // that this new source will get Prepare called on this current pump
+ // iteration since it gets added at the end of the source list.
+ if (!state->idle_source) {
+ state->idle_source =
+ self->AddSource(&IdleSourceFuncs, G_PRIORITY_DEFAULT_IDLE, NULL);
+ }
+
+ return FALSE;
+}
+
+// static
+gboolean MessagePumpForUI::WorkSourceCheck(GSource* source) {
+ MessagePumpForUI* self = static_cast<WorkSource*>(source)->self;
+ RunState* state = self->state_;
+
+ // Make sure we don't attempt idle work until we are really sure we don't
+ // have other work to do. We'll know this in the call to Prepare.
+ state->should_do_idle_work = false;
+
+ // Check if ScheduleWork or ScheduleDelayedWork was called. This is a
+ // non-blocking read.
+ char byte;
+ while (0 < read(self->read_fd_work_scheduled_, &byte, 1)) {
+ // Don't assume we actually have work yet unless the stronger ScheduleWork
+ // was called.
+ if (byte == kWorkScheduled)
+ state->should_do_work = true;
+ }
+
+ if (state->should_do_work)
+ return TRUE;
+
+ if (!self->delayed_work_time_.is_null())
+ return self->delayed_work_time_ <= Time::Now();
+
+ return FALSE;
+}
+
+// static
+gboolean MessagePumpForUI::WorkSourceDispatch(GSource* source,
+ GSourceFunc unused_func,
+ gpointer unused_data) {
+ MessagePumpForUI* self = static_cast<WorkSource*>(source)->self;
+ RunState* state = self->state_;
+ DCHECK(!state->should_do_idle_work) <<
+ "Idle work should not be flagged while regular work exists.";
+
+ // Note that in this function we never return FALSE. This source is owned
+ // by GLib and shared by multiple calls to Run. It will only finally get
+ // destroyed when the loop is destroyed.
+
+ state->should_do_work = state->delegate->DoWork();
+ if (!state->keep_running)
+ return TRUE;
+
+ state->should_do_work |=
+ state->delegate->DoDelayedWork(&self->delayed_work_time_);
+
+ return TRUE;
+}
+
+// static
+gboolean MessagePumpForUI::IdleSourcePrepare(GSource* source,
+ gint* timeout_ms) {
+ RunState* state = static_cast<WorkSource*>(source)->self->state_;
+ *timeout_ms = 0;
+ return state->should_do_idle_work;
+}
+
+// static
+gboolean MessagePumpForUI::IdleSourceCheck(GSource* source) {
+ RunState* state = static_cast<WorkSource*>(source)->self->state_;
+ return state->should_do_idle_work;
+}
+
+// static
+gboolean MessagePumpForUI::IdleSourceDispatch(GSource* source,
+ GSourceFunc unused_func,
+ gpointer unused_data) {
+ RunState* state = static_cast<WorkSource*>(source)->self->state_;
+ // We should not do idle work unless we didn't have other work to do.
+ DCHECK(!state->should_do_work) << "Doing idle work in non-idle time!";
+ state->should_do_idle_work = false;
+ state->should_do_work = state->delegate->DoIdleWork();
+
+ // This is an optimization. We could always remove ourselves right now,
+ // but we will just get re-added when WorkSourceCheck eventually returns
+ // FALSE.
+ if (!state->should_do_work) {
+ // This is so that when we return FALSE, GLib will not only remove us
+ // from the context, but since it holds the last reference, it will
+ // destroy us as well.
+ g_source_unref(source);
+ state->idle_source = NULL;
+ }
+
+ return state->should_do_work;
+}
+
+GSource* MessagePumpForUI::AddSource(GSourceFuncs* funcs, gint priority,
+ GPollFD *optional_poll_fd) {
+ GSource* source = g_source_new(funcs, sizeof(WorkSource));
+
+ // Setting the priority is actually a bit expensive since it causes GLib
+ // to resort an internal list.
+ if (priority != G_PRIORITY_DEFAULT)
+ g_source_set_priority(source, priority);
+
+ // This is needed to allow Run calls inside Dispatch.
+ g_source_set_can_recurse(source, TRUE);
+ static_cast<WorkSource*>(source)->self = this;
+
+ if (optional_poll_fd)
+ g_source_add_poll(source, optional_poll_fd);
+
+ g_source_attach(source, context_);
+ return source;
+}
+
+} // namespace base