| // Copyright (c) 2016 The Chromium Embedded Framework 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 "tests/shared/browser/main_message_loop_external_pump.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <math.h> |
| |
| #include <glib.h> |
| |
| #include "include/base/cef_logging.h" |
| #include "include/cef_app.h" |
| |
| // From base/posix/eintr_wrapper.h. |
| // This provides a wrapper around system calls which may be interrupted by a |
| // signal and return EINTR. See man 7 signal. |
| // To prevent long-lasting loops (which would likely be a bug, such as a signal |
| // that should be masked) to go unnoticed, there is a limit after which the |
| // caller will nonetheless see an EINTR in Debug builds. |
| #if !defined(HANDLE_EINTR) |
| #if !DCHECK_IS_ON() |
| |
| #define HANDLE_EINTR(x) \ |
| ({ \ |
| decltype(x) eintr_wrapper_result; \ |
| do { \ |
| eintr_wrapper_result = (x); \ |
| } while (eintr_wrapper_result == -1 && errno == EINTR); \ |
| eintr_wrapper_result; \ |
| }) |
| |
| #else |
| |
| #define HANDLE_EINTR(x) \ |
| ({ \ |
| int eintr_wrapper_counter = 0; \ |
| decltype(x) eintr_wrapper_result; \ |
| do { \ |
| eintr_wrapper_result = (x); \ |
| } while (eintr_wrapper_result == -1 && errno == EINTR && \ |
| eintr_wrapper_counter++ < 100); \ |
| eintr_wrapper_result; \ |
| }) |
| |
| #endif // !DCHECK_IS_ON() |
| #endif // !defined(HANDLE_EINTR) |
| |
| namespace client { |
| |
| namespace { |
| |
| class MainMessageLoopExternalPumpLinux : public MainMessageLoopExternalPump { |
| public: |
| MainMessageLoopExternalPumpLinux(); |
| ~MainMessageLoopExternalPumpLinux(); |
| |
| // MainMessageLoopStd methods: |
| void Quit() OVERRIDE; |
| int Run() OVERRIDE; |
| |
| // MainMessageLoopExternalPump methods: |
| void OnScheduleMessagePumpWork(int64 delay_ms) OVERRIDE; |
| |
| // Internal methods used for processing the pump callbacks. They are public |
| // for simplicity but should not be used directly. HandlePrepare is called |
| // during the prepare step of glib, and returns a timeout that will be passed |
| // to the poll. HandleCheck is called after the poll has completed, and |
| // returns whether or not HandleDispatch should be called. HandleDispatch is |
| // called if HandleCheck returned true. |
| int HandlePrepare(); |
| bool HandleCheck(); |
| void HandleDispatch(); |
| |
| protected: |
| // MainMessageLoopExternalPump methods: |
| void SetTimer(int64 delay_ms) OVERRIDE; |
| void KillTimer() OVERRIDE; |
| bool IsTimerPending() OVERRIDE; |
| |
| private: |
| // Used to flag that the Run() invocation should return ASAP. |
| bool should_quit_; |
| |
| // 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_; |
| |
| // The work source. It is destroyed when the message pump is destroyed. |
| GSource* work_source_; |
| |
| // The time when we need to do delayed work. |
| CefTime delayed_work_time_; |
| |
| // We use a wakeup pipe to make sure we'll get out of the glib polling phase |
| // when another thread has scheduled us to do some work. There is a glib |
| // mechanism g_main_context_wakeup, but this won't guarantee that our event's |
| // Dispatch() will be called. |
| int wakeup_pipe_read_; |
| int wakeup_pipe_write_; |
| |
| // Use a scoped_ptr to avoid needing the definition of GPollFD in the header. |
| scoped_ptr<GPollFD> wakeup_gpollfd_; |
| }; |
| |
| // Return a timeout suitable for the glib loop, -1 to block forever, |
| // 0 to return right away, or a timeout in milliseconds from now. |
| int GetTimeIntervalMilliseconds(const CefTime& from) { |
| if (from.GetDoubleT() == 0.0) |
| return -1; |
| |
| CefTime now; |
| now.Now(); |
| |
| // Be careful here. CefTime 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. |
| int delay = |
| static_cast<int>(ceil((from.GetDoubleT() - now.GetDoubleT()) * 1000.0)); |
| |
| // If this value is negative, then we need to run delayed work soon. |
| return delay < 0 ? 0 : delay; |
| } |
| |
| struct WorkSource : public GSource { |
| MainMessageLoopExternalPumpLinux* pump; |
| }; |
| |
| gboolean WorkSourcePrepare(GSource* source, gint* timeout_ms) { |
| *timeout_ms = static_cast<WorkSource*>(source)->pump->HandlePrepare(); |
| // We always return FALSE, so that our timeout is honored. If we were |
| // to return TRUE, the timeout would be considered to be 0 and the poll |
| // would never block. Once the poll is finished, Check will be called. |
| return FALSE; |
| } |
| |
| gboolean WorkSourceCheck(GSource* source) { |
| // Only return TRUE if Dispatch should be called. |
| return static_cast<WorkSource*>(source)->pump->HandleCheck(); |
| } |
| |
| gboolean WorkSourceDispatch(GSource* source, |
| GSourceFunc unused_func, |
| gpointer unused_data) { |
| static_cast<WorkSource*>(source)->pump->HandleDispatch(); |
| // Always return TRUE so our source stays registered. |
| return TRUE; |
| } |
| |
| // I wish these could be const, but g_source_new wants non-const. |
| GSourceFuncs WorkSourceFuncs = {WorkSourcePrepare, WorkSourceCheck, |
| WorkSourceDispatch, NULL}; |
| |
| MainMessageLoopExternalPumpLinux::MainMessageLoopExternalPumpLinux() |
| : should_quit_(false), |
| context_(g_main_context_default()), |
| wakeup_gpollfd_(new GPollFD) { |
| // Create our wakeup pipe, which is used to flag when work was scheduled. |
| int fds[2]; |
| int ret = pipe(fds); |
| DCHECK_EQ(ret, 0); |
| (void)ret; // Prevent warning in release mode. |
| |
| wakeup_pipe_read_ = fds[0]; |
| wakeup_pipe_write_ = fds[1]; |
| wakeup_gpollfd_->fd = wakeup_pipe_read_; |
| wakeup_gpollfd_->events = G_IO_IN; |
| |
| work_source_ = g_source_new(&WorkSourceFuncs, sizeof(WorkSource)); |
| static_cast<WorkSource*>(work_source_)->pump = this; |
| g_source_add_poll(work_source_, wakeup_gpollfd_.get()); |
| // Use a low priority so that we let other events in the queue go first. |
| g_source_set_priority(work_source_, G_PRIORITY_DEFAULT_IDLE); |
| // This is needed to allow Run calls inside Dispatch. |
| g_source_set_can_recurse(work_source_, TRUE); |
| g_source_attach(work_source_, context_); |
| } |
| |
| MainMessageLoopExternalPumpLinux::~MainMessageLoopExternalPumpLinux() { |
| g_source_destroy(work_source_); |
| g_source_unref(work_source_); |
| close(wakeup_pipe_read_); |
| close(wakeup_pipe_write_); |
| } |
| |
| void MainMessageLoopExternalPumpLinux::Quit() { |
| should_quit_ = true; |
| } |
| |
| int MainMessageLoopExternalPumpLinux::Run() { |
| // We really only do a single task for each iteration of the loop. If we |
| // have done something, assume there is likely something more to do. This |
| // will mean that we don't block on the message pump until there was nothing |
| // more to do. We also set this to true to make sure not to block on the |
| // first iteration of the loop. |
| bool more_work_is_plausible = true; |
| |
| // We run our own loop instead of using g_main_loop_quit in one of the |
| // callbacks. This is so we only quit our own loops, and we don't quit |
| // nested loops run by others. |
| for (;;) { |
| // Don't block if we think we have more work to do. |
| bool block = !more_work_is_plausible; |
| |
| more_work_is_plausible = g_main_context_iteration(context_, block); |
| if (should_quit_) |
| break; |
| } |
| |
| // We need to run the message pump until it is idle. However we don't have |
| // that information here so we run the message loop "for a while". |
| for (int i = 0; i < 10; ++i) { |
| // Do some work. |
| CefDoMessageLoopWork(); |
| |
| // Sleep to allow the CEF proc to do work. |
| usleep(50000); |
| } |
| |
| return 0; |
| } |
| |
| void MainMessageLoopExternalPumpLinux::OnScheduleMessagePumpWork( |
| int64 delay_ms) { |
| // 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. |
| if (HANDLE_EINTR(write(wakeup_pipe_write_, &delay_ms, sizeof(int64))) != |
| sizeof(int64)) { |
| NOTREACHED() << "Could not write to the UI message loop wakeup pipe!"; |
| } |
| } |
| |
| // Return the timeout we want passed to poll. |
| int MainMessageLoopExternalPumpLinux::HandlePrepare() { |
| // We don't think we have work to do, but make sure not to block longer than |
| // the next time we need to run delayed work. |
| return GetTimeIntervalMilliseconds(delayed_work_time_); |
| } |
| |
| bool MainMessageLoopExternalPumpLinux::HandleCheck() { |
| // We usually have a single message on the wakeup pipe, since we are only |
| // signaled when the queue went from empty to non-empty, but there can be |
| // two messages if a task posted a task, hence we read at most two bytes. |
| // The glib poll will tell us whether there was data, so this read shouldn't |
| // block. |
| if (wakeup_gpollfd_->revents & G_IO_IN) { |
| int64 delay_ms[2]; |
| const size_t num_bytes = |
| HANDLE_EINTR(read(wakeup_pipe_read_, delay_ms, sizeof(int64) * 2)); |
| if (num_bytes < sizeof(int64)) { |
| NOTREACHED() << "Error reading from the wakeup pipe."; |
| } |
| if (num_bytes == sizeof(int64)) |
| OnScheduleWork(delay_ms[0]); |
| if (num_bytes == sizeof(int64) * 2) |
| OnScheduleWork(delay_ms[1]); |
| } |
| |
| if (GetTimeIntervalMilliseconds(delayed_work_time_) == 0) { |
| // The timer has expired. That condition will stay true until we process |
| // that delayed work, so we don't need to record this differently. |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void MainMessageLoopExternalPumpLinux::HandleDispatch() { |
| OnTimerTimeout(); |
| } |
| |
| void MainMessageLoopExternalPumpLinux::SetTimer(int64 delay_ms) { |
| DCHECK_GT(delay_ms, 0); |
| |
| CefTime now; |
| now.Now(); |
| |
| delayed_work_time_ = |
| CefTime(now.GetDoubleT() + static_cast<double>(delay_ms) / 1000.0); |
| } |
| |
| void MainMessageLoopExternalPumpLinux::KillTimer() { |
| delayed_work_time_ = CefTime(); |
| } |
| |
| bool MainMessageLoopExternalPumpLinux::IsTimerPending() { |
| return GetTimeIntervalMilliseconds(delayed_work_time_) > 0; |
| } |
| |
| } // namespace |
| |
| // static |
| scoped_ptr<MainMessageLoopExternalPump> MainMessageLoopExternalPump::Create() { |
| return scoped_ptr<MainMessageLoopExternalPump>( |
| new MainMessageLoopExternalPumpLinux()); |
| } |
| |
| } // namespace client |