Modified: trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp (173200 => 173201)
--- trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp 2014-09-03 06:04:44 UTC (rev 173200)
+++ trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp 2014-09-03 07:26:52 UTC (rev 173201)
@@ -28,8 +28,8 @@
#if USE(GLIB)
#include "GMainLoopSource.h"
-
#include <gio/gio.h>
+#include <wtf/gobject/GMutexLocker.h>
namespace WTF {
@@ -42,17 +42,20 @@
: m_deleteOnDestroy(DoNotDeleteOnDestroy)
, m_status(Ready)
{
+ g_mutex_init(&m_mutex);
}
GMainLoopSource::GMainLoopSource(DeleteOnDestroyType deleteOnDestroy)
: m_deleteOnDestroy(deleteOnDestroy)
, m_status(Ready)
{
+ g_mutex_init(&m_mutex);
}
GMainLoopSource::~GMainLoopSource()
{
cancel();
+ g_mutex_clear(&m_mutex);
}
bool GMainLoopSource::isScheduled() const
@@ -67,26 +70,24 @@
void GMainLoopSource::cancel()
{
- if (!m_source)
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+}
+
+void GMainLoopSource::cancelWithoutLocking()
+{
+ if (!m_context.source) {
+ m_status = Ready;
return;
+ }
- GRefPtr<GSource> source;
- m_source.swap(source);
+ Context context = WTF::move(m_context);
- if (m_cancellable)
- g_cancellable_cancel(m_cancellable.get());
- g_source_destroy(source.get());
- destroy();
-}
+ if (context.cancellable)
+ g_cancellable_cancel(context.cancellable.get());
-void GMainLoopSource::reset()
-{
- m_status = Ready;
- m_source = nullptr;
- m_cancellable = nullptr;
- m_voidCallback = nullptr;
- m_boolCallback = nullptr;
- m_destroyCallback = nullptr;
+ g_source_destroy(context.source.get());
+ destroy(context.destroyCallback);
}
void GMainLoopSource::scheduleIdleSource(const char* name, GSourceFunc sourceFunction, int priority, GMainContext* context)
@@ -94,43 +95,46 @@
ASSERT(m_status == Ready);
m_status = Scheduled;
- m_source = adoptGRef(g_idle_source_new());
- g_source_set_name(m_source.get(), name);
+ m_context.source = adoptGRef(g_idle_source_new());
+ g_source_set_name(m_context.source.get(), name);
if (priority != G_PRIORITY_DEFAULT_IDLE)
- g_source_set_priority(m_source.get(), priority);
- g_source_set_callback(m_source.get(), sourceFunction, this, nullptr);
- g_source_attach(m_source.get(), context);
+ g_source_set_priority(m_context.source.get(), priority);
+ g_source_set_callback(m_context.source.get(), sourceFunction, this, nullptr);
+ g_source_attach(m_context.source.get(), context);
}
void GMainLoopSource::schedule(const char* name, std::function<void ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.voidCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
}
void GMainLoopSource::schedule(const char* name, std::function<bool ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.boolCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
}
void GMainLoopSource::schedule(const char* name, std::function<bool (GIOCondition)> function, GSocket* socket, GIOCondition condition, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
ASSERT(m_status == Ready);
m_status = Scheduled;
- m_socketCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
- m_cancellable = adoptGRef(g_cancellable_new());
- m_source = adoptGRef(g_socket_create_source(socket, condition, m_cancellable.get()));
- g_source_set_name(m_source.get(), name);
- g_source_set_callback(m_source.get(), reinterpret_cast<GSourceFunc>(socketSourceCallback), this, nullptr);
- g_source_attach(m_source.get(), context);
+ m_context.socketCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
+ m_context.cancellable = adoptGRef(g_cancellable_new());
+ m_context.source = adoptGRef(g_socket_create_source(socket, condition, m_context.cancellable.get()));
+ g_source_set_name(m_context.source.get(), name);
+ g_source_set_callback(m_context.source.get(), reinterpret_cast<GSourceFunc>(socketSourceCallback), this, nullptr);
+ g_source_attach(m_context.source.get(), context);
}
void GMainLoopSource::scheduleTimeoutSource(const char* name, GSourceFunc sourceFunction, int priority, GMainContext* context)
@@ -138,109 +142,158 @@
ASSERT(m_status == Ready);
m_status = Scheduled;
- ASSERT(m_source);
- g_source_set_name(m_source.get(), name);
+ ASSERT(m_context.source);
+ g_source_set_name(m_context.source.get(), name);
if (priority != G_PRIORITY_DEFAULT)
- g_source_set_priority(m_source.get(), priority);
- g_source_set_callback(m_source.get(), sourceFunction, this, nullptr);
- g_source_attach(m_source.get(), context);
+ g_source_set_priority(m_context.source.get(), priority);
+ g_source_set_callback(m_context.source.get(), sourceFunction, this, nullptr);
+ g_source_attach(m_context.source.get(), context);
}
void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_source = adoptGRef(g_timeout_source_new(delay.count()));
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.source = adoptGRef(g_timeout_source_new(delay.count()));
+ m_context.voidCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
}
void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_source = adoptGRef(g_timeout_source_new(delay.count()));
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.source = adoptGRef(g_timeout_source_new(delay.count()));
+ m_context.boolCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
}
void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
+ m_context.voidCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
}
void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
{
- cancel();
- m_source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+ m_context.source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
+ m_context.boolCallback = WTF::move(function);
+ m_context.destroyCallback = WTF::move(destroyFunction);
scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
}
void GMainLoopSource::voidCallback()
{
- if (!m_source)
- return;
+ Context context;
- ASSERT(m_voidCallback);
- ASSERT(m_status == Scheduled);
- m_status = Dispatched;
+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return;
- GSource* source = m_source.get();
- m_voidCallback();
- if (source == m_source.get())
- destroy();
+ context = WTF::move(m_context);
+
+ ASSERT(context.voidCallback);
+ ASSERT(m_status == Scheduled);
+ m_status = Dispatched;
+ }
+
+ context.voidCallback();
+
+ bool shouldDestroy = false;
+ {
+ GMutexLocker locker(m_mutex);
+ shouldDestroy = !m_context.source;
+ }
+
+ if (shouldDestroy)
+ destroy(context.destroyCallback);
}
bool GMainLoopSource::boolCallback()
{
- if (!m_source)
- return false;
+ Context context;
- ASSERT(m_boolCallback);
- ASSERT(m_status == Scheduled || m_status == Dispatched);
- m_status = Dispatched;
+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return Stop;
- GSource* source = m_source.get();
- bool retval = m_boolCallback();
- if (!retval && source == m_source.get())
- destroy();
+ context = WTF::move(m_context);
+ ASSERT(context.boolCallback);
+ ASSERT(m_status == Scheduled || m_status == Dispatched);
+ m_status = Dispatched;
+ }
+
+ bool retval = context.boolCallback();
+
+ bool shouldDestroy = false;
+ {
+ GMutexLocker locker(m_mutex);
+ if (retval && !m_context.source)
+ m_context = WTF::move(context);
+ else
+ shouldDestroy = !m_context.source;
+ }
+
+ if (shouldDestroy)
+ destroy(context.destroyCallback);
return retval;
}
bool GMainLoopSource::socketCallback(GIOCondition condition)
{
- if (!m_source)
- return false;
+ Context context;
- ASSERT(m_socketCallback);
- ASSERT(m_status == Scheduled || m_status == Dispatched);
- m_status = Dispatched;
+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return Stop;
- if (g_cancellable_is_cancelled(m_cancellable.get())) {
- destroy();
- return false;
+ context = WTF::move(m_context);
+
+ ASSERT(context.socketCallback);
+ ASSERT(m_status == Scheduled || m_status == Dispatched);
+ m_status = Dispatched;
}
- GSource* source = m_source.get();
- bool retval = m_socketCallback(condition);
- if (!retval && source == m_source.get())
- destroy();
+ if (g_cancellable_is_cancelled(context.cancellable.get())) {
+ destroy(context.destroyCallback);
+ return Stop;
+ }
+ bool retval = context.socketCallback(condition);
+
+ bool shouldDestroy = false;
+ {
+ GMutexLocker locker(m_mutex);
+ if (retval && !m_context.source)
+ m_context = WTF::move(context);
+ else
+ shouldDestroy = !m_context.source;
+ }
+
+ if (shouldDestroy)
+ destroy(context.destroyCallback);
return retval;
}
-void GMainLoopSource::destroy()
+void GMainLoopSource::destroy(const std::function<void ()>& destroyCallback)
{
- auto destroyCallback = WTF::move(m_destroyCallback);
- auto deleteOnDestroy = m_deleteOnDestroy;
- reset();
+ // Nothing should be scheduled on this object at this point.
+ ASSERT(!m_context.source);
+ m_status = Ready;
+ DeleteOnDestroyType deleteOnDestroy = m_deleteOnDestroy;
+
if (destroyCallback)
destroyCallback();
Modified: trunk/Source/WTF/wtf/gobject/GMainLoopSource.h (173200 => 173201)
--- trunk/Source/WTF/wtf/gobject/GMainLoopSource.h 2014-09-03 06:04:44 UTC (rev 173200)
+++ trunk/Source/WTF/wtf/gobject/GMainLoopSource.h 2014-09-03 07:26:52 UTC (rev 173201)
@@ -32,6 +32,7 @@
#include <wtf/gobject/GRefPtr.h>
typedef struct _GSocket GSocket;
+typedef union _GMutex GMutex;
namespace WTF {
@@ -65,13 +66,13 @@
enum Status { Ready, Scheduled, Dispatched };
- void reset();
+ void cancelWithoutLocking();
void scheduleIdleSource(const char* name, GSourceFunc, int priority, GMainContext*);
void scheduleTimeoutSource(const char* name, GSourceFunc, int priority, GMainContext*);
void voidCallback();
bool boolCallback();
bool socketCallback(GIOCondition);
- void destroy();
+ void destroy(const std::function<void ()>&);
static gboolean voidSourceCallback(GMainLoopSource*);
static gboolean boolSourceCallback(GMainLoopSource*);
@@ -79,12 +80,20 @@
DeleteOnDestroyType m_deleteOnDestroy;
Status m_status;
- GRefPtr<GSource> m_source;
- GRefPtr<GCancellable> m_cancellable;
- std::function<void ()> m_voidCallback;
- std::function<bool ()> m_boolCallback;
- std::function<bool (GIOCondition)> m_socketCallback;
- std::function<void ()> m_destroyCallback;
+ GMutex m_mutex;
+
+ struct Context {
+ Context() = default;
+ Context(Context&&) = default;
+ Context& operator=(Context&&) = default;
+
+ GRefPtr<GSource> source;
+ GRefPtr<GCancellable> cancellable;
+ std::function<void ()> voidCallback;
+ std::function<bool ()> boolCallback;
+ std::function<bool (GIOCondition)> socketCallback;
+ std::function<void ()> destroyCallback;
+ } m_context;
};
} // namespace WTF
Added: trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp (0 => 173201)
--- trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp 2014-09-03 07:26:52 UTC (rev 173201)
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <wtf/gobject/GMainLoopSource.h>
+#include <stdio.h>
+
+namespace TestWebKitAPI {
+
+class GMainLoopSourceTest {
+public:
+ GMainLoopSourceTest()
+ : m_mainLoop(g_main_loop_new(nullptr, TRUE))
+ {
+ }
+
+ ~GMainLoopSourceTest()
+ {
+ g_main_loop_unref(m_mainLoop);
+ }
+
+ void runLoop()
+ {
+ g_main_loop_run(m_mainLoop);
+ }
+
+ void finish()
+ {
+ g_main_loop_quit(m_mainLoop);
+ }
+
+ GMainLoopSource& source() { return m_source; }
+
+private:
+ GMainLoop* m_mainLoop;
+ GMainLoopSource m_source;
+};
+
+TEST(WTF_GMainLoopSource, BasicRescheduling)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool finishedFirstTask = false;
+ bool finishedSecondTask = false;
+ } context;
+
+ context.test.source().schedule("[Test] FirstTask", [&] {
+ // This should never be called. That's why we assert
+ // that the variable is false a few lines later.
+ context.finishedFirstTask = true;
+ });
+
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+
+ context.test.runLoop();
+ EXPECT_FALSE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+TEST(WTF_GMainLoopSource, ReentrantRescheduling)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool finishedFirstTask = false;
+ bool finishedSecondTask = false;
+ } context;
+
+ context.test.source().schedule("[Test] FirstTask", [&] {
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ ASSERT(context.finishedFirstTask);
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+
+ context.finishedFirstTask = true;
+ });
+
+ context.test.runLoop();
+ EXPECT_TRUE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+TEST(WTF_GMainLoopSource, ReschedulingFromDifferentThread)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool finishedFirstTask;
+ bool finishedSecondTask;
+ } context;
+
+ context.test.source().schedule("[Test] FirstTask", [&] {
+ g_usleep(1 * G_USEC_PER_SEC);
+ context.finishedFirstTask = true;
+ });
+
+ g_thread_new(nullptr, [](gpointer data) -> gpointer {
+ g_usleep(0.25 * G_USEC_PER_SEC);
+
+ TestingContext& context = *static_cast<TestingContext*>(data);
+ EXPECT_FALSE(context.finishedFirstTask);
+
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ EXPECT_TRUE(context.finishedFirstTask);
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+
+ g_thread_exit(nullptr);
+ return nullptr;
+ }, &context);
+
+ context.test.runLoop();
+ EXPECT_TRUE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+} // namespace TestWebKitAPI