Title: [173201] trunk
Revision
173201
Author
[email protected]
Date
2014-09-03 00:26:52 -0700 (Wed, 03 Sep 2014)

Log Message

GMainLoopSource is exposed to race conditions
https://bugs.webkit.org/show_bug.cgi?id=135800

Reviewed by Carlos Garcia Campos.

Source/WTF:

GMainLoopSource objects can be dispatching tasks on one thread
while having a new task scheduled on a different thread. This
can for instance occur in WebKitVideoSink, where the timeout
callback can be called on main thread while at the same time
it is being rescheduled on a different thread (created through
GStreamer).

The initial solution is to use GMutex to prevent parallel data
access from different threads. In the future I plan to add better
assertions, some meaningful comments and look at the possibility
of creating thread-specific GMainLoopSource objects that wouldn't
require the use of GMutex.

GSource, GCancellable and std::function<> objects are now packed
into an internal Context structure. Using the C++11 move semantics
it's simple to, at the time of dispatch, move the current context
out of the GMainLoopSource object in case the dispatch causes a
rescheduling on that same object.

All the schedule*() methods and the cancelInternal() method callers
now lock the GMutex to ensure no one else is accessing the data at
that moment. Similar goes for the dispatch methods, but those do
the dispatch and possible destruction duties with the mutex unlocked.
The dispatch can cause rescheduling on the same GMainLoopSource object,
which must not be done with a locked mutex.

* wtf/gobject/GMainLoopSource.cpp:
(WTF::GMainLoopSource::GMainLoopSource):
(WTF::GMainLoopSource::~GMainLoopSource):
(WTF::GMainLoopSource::cancel):
(WTF::GMainLoopSource::cancelInternal):
(WTF::GMainLoopSource::scheduleIdleSource):
(WTF::GMainLoopSource::schedule):
(WTF::GMainLoopSource::scheduleTimeoutSource):
(WTF::GMainLoopSource::scheduleAfterDelay):
(WTF::GMainLoopSource::voidCallback):
(WTF::GMainLoopSource::boolCallback):
(WTF::GMainLoopSource::socketCallback):
(WTF::GMainLoopSource::destroy):
(WTF::GMainLoopSource::reset): Deleted.
* wtf/gobject/GMainLoopSource.h:

Tools:

Add a unit test for GMainLoopSource that tests different
types of rescheduling tasks on already-active sources.

* TestWebKitAPI/PlatformGTK.cmake:
* TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp: Added.
(GMainLoopSourceTest::GMainLoopSourceTest):
(GMainLoopSourceTest::~GMainLoopSourceTest):
(GMainLoopSourceTest::runLoop):
(GMainLoopSourceTest::finish):
(GMainLoopSourceTest::source):
(testGMainLoopSourceBasicRescheduling):
(testGMainLoopSourceReentrantRescheduling):
(testGMainLoopSourceDifferentThreadRescheduling):
(beforeAll):
(afterAll):
(TestWebKitAPI::GMainLoopSourceTest::GMainLoopSourceTest):
(TestWebKitAPI::GMainLoopSourceTest::~GMainLoopSourceTest):
(TestWebKitAPI::GMainLoopSourceTest::runLoop):
(TestWebKitAPI::GMainLoopSourceTest::finish):
(TestWebKitAPI::GMainLoopSourceTest::source):
(TestWebKitAPI::TEST):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WTF/ChangeLog (173200 => 173201)


--- trunk/Source/WTF/ChangeLog	2014-09-03 06:04:44 UTC (rev 173200)
+++ trunk/Source/WTF/ChangeLog	2014-09-03 07:26:52 UTC (rev 173201)
@@ -1,3 +1,52 @@
+2014-09-03  Zan Dobersek  <[email protected]>
+
+        GMainLoopSource is exposed to race conditions
+        https://bugs.webkit.org/show_bug.cgi?id=135800
+
+        Reviewed by Carlos Garcia Campos.
+
+        GMainLoopSource objects can be dispatching tasks on one thread
+        while having a new task scheduled on a different thread. This
+        can for instance occur in WebKitVideoSink, where the timeout
+        callback can be called on main thread while at the same time
+        it is being rescheduled on a different thread (created through
+        GStreamer).
+
+        The initial solution is to use GMutex to prevent parallel data
+        access from different threads. In the future I plan to add better
+        assertions, some meaningful comments and look at the possibility
+        of creating thread-specific GMainLoopSource objects that wouldn't
+        require the use of GMutex.
+
+        GSource, GCancellable and std::function<> objects are now packed
+        into an internal Context structure. Using the C++11 move semantics
+        it's simple to, at the time of dispatch, move the current context
+        out of the GMainLoopSource object in case the dispatch causes a
+        rescheduling on that same object.
+
+        All the schedule*() methods and the cancelInternal() method callers
+        now lock the GMutex to ensure no one else is accessing the data at
+        that moment. Similar goes for the dispatch methods, but those do
+        the dispatch and possible destruction duties with the mutex unlocked.
+        The dispatch can cause rescheduling on the same GMainLoopSource object,
+        which must not be done with a locked mutex.
+
+        * wtf/gobject/GMainLoopSource.cpp:
+        (WTF::GMainLoopSource::GMainLoopSource):
+        (WTF::GMainLoopSource::~GMainLoopSource):
+        (WTF::GMainLoopSource::cancel):
+        (WTF::GMainLoopSource::cancelInternal):
+        (WTF::GMainLoopSource::scheduleIdleSource):
+        (WTF::GMainLoopSource::schedule):
+        (WTF::GMainLoopSource::scheduleTimeoutSource):
+        (WTF::GMainLoopSource::scheduleAfterDelay):
+        (WTF::GMainLoopSource::voidCallback):
+        (WTF::GMainLoopSource::boolCallback):
+        (WTF::GMainLoopSource::socketCallback):
+        (WTF::GMainLoopSource::destroy):
+        (WTF::GMainLoopSource::reset): Deleted.
+        * wtf/gobject/GMainLoopSource.h:
+
 2014-09-02  Daniel Bates  <[email protected]>
 
         [iOS] Support using Foundation networking code

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

Modified: trunk/Tools/ChangeLog (173200 => 173201)


--- trunk/Tools/ChangeLog	2014-09-03 06:04:44 UTC (rev 173200)
+++ trunk/Tools/ChangeLog	2014-09-03 07:26:52 UTC (rev 173201)
@@ -1,3 +1,32 @@
+2014-09-03  Zan Dobersek  <[email protected]>
+
+        GMainLoopSource is exposed to race conditions
+        https://bugs.webkit.org/show_bug.cgi?id=135800
+
+        Reviewed by Carlos Garcia Campos.
+
+        Add a unit test for GMainLoopSource that tests different
+        types of rescheduling tasks on already-active sources.
+
+        * TestWebKitAPI/PlatformGTK.cmake:
+        * TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp: Added.
+        (GMainLoopSourceTest::GMainLoopSourceTest):
+        (GMainLoopSourceTest::~GMainLoopSourceTest):
+        (GMainLoopSourceTest::runLoop):
+        (GMainLoopSourceTest::finish):
+        (GMainLoopSourceTest::source):
+        (testGMainLoopSourceBasicRescheduling):
+        (testGMainLoopSourceReentrantRescheduling):
+        (testGMainLoopSourceDifferentThreadRescheduling):
+        (beforeAll):
+        (afterAll):
+        (TestWebKitAPI::GMainLoopSourceTest::GMainLoopSourceTest):
+        (TestWebKitAPI::GMainLoopSourceTest::~GMainLoopSourceTest):
+        (TestWebKitAPI::GMainLoopSourceTest::runLoop):
+        (TestWebKitAPI::GMainLoopSourceTest::finish):
+        (TestWebKitAPI::GMainLoopSourceTest::source):
+        (TestWebKitAPI::TEST):
+
 2014-09-02  Simon Fraser  <[email protected]>
 
         Make sure WK1 prefs are initialized in MiniBrowser

Modified: trunk/Tools/TestWebKitAPI/PlatformGTK.cmake (173200 => 173201)


--- trunk/Tools/TestWebKitAPI/PlatformGTK.cmake	2014-09-03 06:04:44 UTC (rev 173200)
+++ trunk/Tools/TestWebKitAPI/PlatformGTK.cmake	2014-09-03 07:26:52 UTC (rev 173201)
@@ -136,5 +136,6 @@
 set_target_properties(TestWebCore PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebCore)
 
 list(APPEND TestWTF_SOURCES
+    ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GMainLoopSource.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GUniquePtr.cpp
 )

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
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to