diff options
author | dsh@google.com <dsh@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-03 16:52:59 +0000 |
---|---|---|
committer | dsh@google.com <dsh@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-03 16:52:59 +0000 |
commit | 8fc3a4815737707de36239119a3d3c648b826e2f (patch) | |
tree | 212caac6e386552d01794b68fbae74722cea456f | |
parent | 748f1130a5fc046b5042390dd08b27df4b64c0cc (diff) | |
download | chromium_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
-rw-r--r-- | base/SConscript | 2 | ||||
-rw-r--r-- | base/message_loop.cc | 14 | ||||
-rw-r--r-- | base/message_pump_glib.cc | 304 | ||||
-rw-r--r-- | base/message_pump_glib.h | 100 | ||||
-rw-r--r-- | build/SConscript.main | 3 |
5 files changed, 415 insertions, 8 deletions
diff --git a/base/SConscript b/base/SConscript index 939ab6e..41693a5 100644 --- a/base/SConscript +++ b/base/SConscript @@ -168,6 +168,7 @@ if env['PLATFORM'] == 'posix': 'base_paths_linux.cc', 'file_util_linux.cc', 'hmac_nss.cc', + 'message_pump_glib.cc', 'nss_init.cc', 'sys_string_conversions_linux.cc', 'worker_pool.cc', @@ -351,4 +352,3 @@ env_tests.StaticObject('perftimer.cc') # Since run_all_perftests supplies a main, we cannot have it in base.lib env_tests.StaticObject('run_all_perftests.cc') - diff --git a/base/message_loop.cc b/base/message_loop.cc index 60a8ce5..348648f 100644 --- a/base/message_loop.cc +++ b/base/message_loop.cc @@ -19,6 +19,9 @@ #if defined(OS_POSIX) #include "base/message_pump_libevent.h" #endif +#if defined(OS_LINUX) +#include "base/message_pump_glib.h" +#endif // A lazily created thread local storage for quick access to a thread's message // loop, if one exists. This should be safe and free of static constructors. @@ -89,18 +92,17 @@ MessageLoop::MessageLoop(Type type) pump_ = new base::MessagePumpForUI(); } #elif defined(OS_POSIX) -#if defined(OS_MACOSX) if (type_ == TYPE_UI) { +#if defined(OS_MACOSX) pump_ = base::MessagePumpMac::Create(); - } else -#endif // OS_MACOSX - if (type_ == TYPE_IO) { +#elif defined(OS_LINUX) + pump_ = new base::MessagePumpForUI(); +#endif // OS_LINUX + } else if (type_ == TYPE_IO) { pump_ = new base::MessagePumpLibevent(); } else { pump_ = new base::MessagePumpDefault(); } -#else // OS_POSIX - pump_ = new base::MessagePumpDefault(); #endif // OS_POSIX } 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 diff --git a/base/message_pump_glib.h b/base/message_pump_glib.h new file mode 100644 index 0000000..97daa8a --- /dev/null +++ b/base/message_pump_glib.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef BASE_MESSAGE_PUMP_GLIB_H_ +#define BASE_MESSAGE_PUMP_GLIB_H_ + +#include <glib.h> + +#include "base/message_pump.h" +#include "base/time.h" + +namespace base { + +// This class implements a MessagePump needed for TYPE_UI MessageLoops on +// OS_LINUX platforms using GLib. +class MessagePumpForUI : public MessagePump { + public: + MessagePumpForUI(); + ~MessagePumpForUI(); + + virtual void Run(Delegate* delegate); + virtual void Quit(); + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const Time& delayed_work_time); + + private: + // We may make recursive calls to Run, so we save state that needs to be + // separate between them in this structure type. + struct RunState { + // This is the delegate argument passed to Run. + Delegate* delegate; + // This tells us when to exit the event pump. + bool keep_running; + // This tells our work source when to dispatch DoWork and DoDelayedWork. + bool should_do_work; + // This tells our idle source when to dispatch DoIdleWork. + bool should_do_idle_work; + // Unlike the work source, which is shared by all calls to Run, each Run + // call gets its own idle source because we need to destroy it when we have + // no idle work, and we don't want to destroy someone else's source. + GSource* idle_source; + }; + + struct WorkSource : GSource { + MessagePumpForUI* self; + }; + + // The source with these callbacks remain in the main loop forever. They + // will dispatch DoWork and DoDelayedWork, and calculate when and how long + // to block when GLib calls poll internally. + static GSourceFuncs WorkSourceFuncs; + static gboolean WorkSourcePrepare(GSource* source, gint* timeout_ms); + static gboolean WorkSourceCheck(GSource* source); + static gboolean WorkSourceDispatch(GSource* source, GSourceFunc unused_func, + gpointer unused_data); + + // The source that uses these callbacks is added as an idle source, which + // means GLib will call it when there is no other work to do. We continue + // doing work as long as DoIdleWork or the other work functions return true. + // Once no work remains, we remove the idle source so GLib will block instead + // of firing it. Then we re-add it when we wake up. + static GSourceFuncs IdleSourceFuncs; + static gboolean IdleSourcePrepare(GSource* source, gint* timeout_ms); + static gboolean IdleSourceCheck(GSource* source); + static gboolean IdleSourceDispatch(GSource* source, GSourceFunc unused_func, + gpointer unused_data); + + // This adds a GLib source to the main loop. + GSource* AddSource(GSourceFuncs* funcs, gint priority, + GPollFD* optional_poll_fd); + + RunState* state_; + + // This is a GLib structure that we can add event sources to. We use the + // default GLib context, which is the one to which all GTK events are + // dispatched. + GMainContext* context_; + + // This is the time when we need to do delayed work. + Time delayed_work_time_; + + // We use a pipe to schedule work in a thread-safe way that doesn't interfere + // with our state. When ScheduleWork is called, we write into the pipe which + // ensures poll will not sleep, since we use the read end as an event source. + // When we find data pending on the pipe, we clear it out and know we have + // been given new work. + int write_fd_work_scheduled_; + int read_fd_work_scheduled_; + + // The work source. It is shared by all calls to Run and destroyed when + // the message pump is destroyed. + GSource* work_source_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpForUI); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_GLIB_H_ diff --git a/build/SConscript.main b/build/SConscript.main index cfe24b4..a4309e2 100644 --- a/build/SConscript.main +++ b/build/SConscript.main @@ -347,8 +347,9 @@ elif env['PLATFORM'] == 'posix': env.Append(CCFLAGS=['-fprofile-arcs', '-ftest-coverage']) env.Append(LINKFLAGS=['-fprofile-arcs']) - # Build with system-provided NSS + # Build with system-provided NSS and GLib env.ParseConfig('pkg-config --cflags --libs nss') + env.ParseConfig('pkg-config --cflags --libs glib-2.0') elif env['PLATFORM'] == 'darwin': |