<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[173720] trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/173720">173720</a></dd>
<dt>Author</dt> <dd>zandobersek@gmail.com</dd>
<dt>Date</dt> <dd>2014-09-18 06:06:59 -0700 (Thu, 18 Sep 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>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 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.
Also added in the Context struct is a new GCancellable. The pointer
of that object is shared with the GMainLoopSource before the Context
is moved out for the callback dispatch. This makes it safe to cancel
or even delete the GMainLoopSource during the dispatch and prevents
use-after-delete on GMainLoopSource once the dispatch is done in
the GMainLoopSource::*Callback() methods.
All the schedule*() methods and the cancelWithoutLocking() 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::cancelWithoutLocking):
(WTF::GMainLoopSource::scheduleIdleSource):
(WTF::GMainLoopSource::schedule):
(WTF::GMainLoopSource::scheduleTimeoutSource):
(WTF::GMainLoopSource::scheduleAfterDelay):
(WTF::GMainLoopSource::voidCallback):
(WTF::GMainLoopSource::boolCallback):
(WTF::GMainLoopSource::socketCallback):
(WTF::GMainLoopSource::socketSourceCallback):
(WTF::GMainLoopSource::Context::destroySource):
(WTF::GMainLoopSource::reset): Deleted.
(WTF::GMainLoopSource::destroy): Deleted.
* wtf/gobject/GMainLoopSource.h:
Tools:
Add unit tests for GMainLoopSource.
The tests check correct behavior of GMainLoopSource in various conditions --
from the most simple rescheduling to rescheduling during dispatch, cancelling
or destroying the GMainLoopSource during dispatch, proper destroy callback
dispatching etc.
Scheduling both void (one-time) and bool (repeatable) callbacks is tested.
State of the GMainLoopSource object (either ready, sheduled or active) is
thoroughly tested throughout the lifetime of that object.
Still missing are tests for socket callbacks, which are a bit trickier because
they rely on a GSocket object. The delete-on-destroy GMainLoopSource objects
are also not tested thoroughly, simply because it is at the moment impossible
to test that the objects are actually destroyed when the corresponding source
is finally deleted.
* TestWebKitAPI/PlatformGTK.cmake:
* TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp: Added.
(TestWebKitAPI::GMainLoopSourceTest::GMainLoopSourceTest):
(TestWebKitAPI::GMainLoopSourceTest::~GMainLoopSourceTest):
(TestWebKitAPI::GMainLoopSourceTest::runLoop):
(TestWebKitAPI::GMainLoopSourceTest::delayedFinish):
(TestWebKitAPI::GMainLoopSourceTest::finish):
(TestWebKitAPI::GMainLoopSourceTest::source):
(TestWebKitAPI::TEST):</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWTFChangeLog">trunk/Source/WTF/ChangeLog</a></li>
<li><a href="#trunkSourceWTFwtfgobjectGMainLoopSourcecpp">trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp</a></li>
<li><a href="#trunkSourceWTFwtfgobjectGMainLoopSourceh">trunk/Source/WTF/wtf/gobject/GMainLoopSource.h</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPIPlatformGTKcmake">trunk/Tools/TestWebKitAPI/PlatformGTK.cmake</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkToolsTestWebKitAPITestsWTFgobjectGMainLoopSourcecpp">trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWTFChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/ChangeLog (173719 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/ChangeLog        2014-09-18 11:16:23 UTC (rev 173719)
+++ trunk/Source/WTF/ChangeLog        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -1,3 +1,60 @@
</span><ins>+2014-09-18 Zan Dobersek <zdobersek@igalia.com>
+
+ 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 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.
+
+ Also added in the Context struct is a new GCancellable. The pointer
+ of that object is shared with the GMainLoopSource before the Context
+ is moved out for the callback dispatch. This makes it safe to cancel
+ or even delete the GMainLoopSource during the dispatch and prevents
+ use-after-delete on GMainLoopSource once the dispatch is done in
+ the GMainLoopSource::*Callback() methods.
+
+ All the schedule*() methods and the cancelWithoutLocking() 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::cancelWithoutLocking):
+ (WTF::GMainLoopSource::scheduleIdleSource):
+ (WTF::GMainLoopSource::schedule):
+ (WTF::GMainLoopSource::scheduleTimeoutSource):
+ (WTF::GMainLoopSource::scheduleAfterDelay):
+ (WTF::GMainLoopSource::voidCallback):
+ (WTF::GMainLoopSource::boolCallback):
+ (WTF::GMainLoopSource::socketCallback):
+ (WTF::GMainLoopSource::socketSourceCallback):
+ (WTF::GMainLoopSource::Context::destroySource):
+ (WTF::GMainLoopSource::reset): Deleted.
+ (WTF::GMainLoopSource::destroy): Deleted.
+ * wtf/gobject/GMainLoopSource.h:
+
</ins><span class="cx"> 2014-09-17 Daniel Bates <dabates@apple.com>
</span><span class="cx">
</span><span class="cx"> Unreviewed, rolling out r173695.
</span></span></pre></div>
<a id="trunkSourceWTFwtfgobjectGMainLoopSourcecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp (173719 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp        2014-09-18 11:16:23 UTC (rev 173719)
+++ trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -28,8 +28,8 @@
</span><span class="cx"> #if USE(GLIB)
</span><span class="cx">
</span><span class="cx"> #include "GMainLoopSource.h"
</span><del>-
</del><span class="cx"> #include <gio/gio.h>
</span><ins>+#include <wtf/gobject/GMutexLocker.h>
</ins><span class="cx">
</span><span class="cx"> namespace WTF {
</span><span class="cx">
</span><span class="lines">@@ -42,17 +42,20 @@
</span><span class="cx"> : m_deleteOnDestroy(DoNotDeleteOnDestroy)
</span><span class="cx"> , m_status(Ready)
</span><span class="cx"> {
</span><ins>+ g_mutex_init(&m_mutex);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> GMainLoopSource::GMainLoopSource(DeleteOnDestroyType deleteOnDestroy)
</span><span class="cx"> : m_deleteOnDestroy(deleteOnDestroy)
</span><span class="cx"> , m_status(Ready)
</span><span class="cx"> {
</span><ins>+ g_mutex_init(&m_mutex);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> GMainLoopSource::~GMainLoopSource()
</span><span class="cx"> {
</span><span class="cx"> cancel();
</span><ins>+ g_mutex_clear(&m_mutex);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> bool GMainLoopSource::isScheduled() const
</span><span class="lines">@@ -67,26 +70,34 @@
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::cancel()
</span><span class="cx"> {
</span><del>- if (!m_source)
- return;
-
- GRefPtr<GSource> source;
- m_source.swap(source);
-
- if (m_cancellable)
- g_cancellable_cancel(m_cancellable.get());
- g_source_destroy(source.get());
- destroy();
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-void GMainLoopSource::reset()
</del><ins>+void GMainLoopSource::cancelWithoutLocking()
</ins><span class="cx"> {
</span><ins>+ // A valid context should only be present if GMainLoopSource is in the Scheduled or Dispatching state.
+ ASSERT(!m_context.source || m_status == Scheduled || m_status == Dispatching);
+ // The general cancellable object should only be present if we're currently dispatching this GMainLoopSource.
+ ASSERT(!m_cancellable || m_status == Dispatching);
+ // Delete-on-destroy GMainLoopSource objects can only be cancelled when there's callback either scheduled
+ // or in the middle of dispatch. At that point cancellation will have no effect.
+ ASSERT(m_deleteOnDestroy != DeleteOnDestroy || (m_status == Ready && !m_context.source));
+
</ins><span class="cx"> m_status = Ready;
</span><del>- m_source = nullptr;
</del><ins>+
+ // The source is perhaps being cancelled in the middle of a callback dispatch.
+ // Cancelling this GCancellable object will convey this information to the
+ // current execution context when the callback dispatch is finished.
+ g_cancellable_cancel(m_cancellable.get());
</ins><span class="cx"> m_cancellable = nullptr;
</span><del>- m_voidCallback = nullptr;
- m_boolCallback = nullptr;
- m_destroyCallback = nullptr;
</del><ins>+ g_cancellable_cancel(m_context.socketCancellable.get());
+
+ if (!m_context.source)
+ return;
+
+ Context context = WTF::move(m_context);
+ context.destroySource();
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleIdleSource(const char* name, GSourceFunc sourceFunction, int priority, GMainContext* context)
</span><span class="lines">@@ -94,43 +105,74 @@
</span><span class="cx"> ASSERT(m_status == Ready);
</span><span class="cx"> m_status = Scheduled;
</span><span class="cx">
</span><del>- m_source = adoptGRef(g_idle_source_new());
- g_source_set_name(m_source.get(), name);
</del><ins>+ g_source_set_name(m_context.source.get(), name);
</ins><span class="cx"> if (priority != G_PRIORITY_DEFAULT_IDLE)
</span><del>- 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);
</del><ins>+ 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::schedule(const char* name, std::function<void ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_idle_source_new()),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ WTF::move(function),
+ nullptr, // boolCallback
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::schedule(const char* name, std::function<bool ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_idle_source_new()),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ nullptr, // voidCallback
+ WTF::move(function),
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::schedule(const char* name, std::function<bool (GIOCondition)> function, GSocket* socket, GIOCondition condition, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ // Don't allow scheduling GIOCondition callbacks on delete-on-destroy GMainLoopSources.
+ ASSERT(m_deleteOnDestroy == DoNotDeleteOnDestroy);
+
+ ASSERT(!m_context.source);
+ GCancellable* socketCancellable = g_cancellable_new();
+ m_context = {
+ adoptGRef(g_socket_create_source(socket, condition, socketCancellable)),
+ adoptGRef(g_cancellable_new()),
+ adoptGRef(socketCancellable),
+ nullptr, // voidCallback
+ nullptr, // boolCallback
+ WTF::move(function),
+ WTF::move(destroyFunction)
+ };
+
</ins><span class="cx"> ASSERT(m_status == Ready);
</span><span class="cx"> m_status = Scheduled;
</span><del>-
- 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);
</del><ins>+ 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleTimeoutSource(const char* name, GSourceFunc sourceFunction, int priority, GMainContext* context)
</span><span class="lines">@@ -138,116 +180,218 @@
</span><span class="cx"> ASSERT(m_status == Ready);
</span><span class="cx"> m_status = Scheduled;
</span><span class="cx">
</span><del>- ASSERT(m_source);
- g_source_set_name(m_source.get(), name);
</del><ins>+ g_source_set_name(m_context.source.get(), name);
</ins><span class="cx"> if (priority != G_PRIORITY_DEFAULT)
</span><del>- 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);
</del><ins>+ 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_source = adoptGRef(g_timeout_source_new(delay.count()));
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_timeout_source_new(delay.count())),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ WTF::move(function),
+ nullptr, // boolCallback
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_source = adoptGRef(g_timeout_source_new(delay.count()));
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_timeout_source_new(delay.count())),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ nullptr, // voidCallback
+ WTF::move(function),
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
- m_voidCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_timeout_source_new_seconds(delay.count())),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ WTF::move(function),
+ nullptr, // boolCallback
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context)
</span><span class="cx"> {
</span><del>- cancel();
- m_source = adoptGRef(g_timeout_source_new_seconds(delay.count()));
- m_boolCallback = WTF::move(function);
- m_destroyCallback = WTF::move(destroyFunction);
</del><ins>+ GMutexLocker locker(m_mutex);
+ cancelWithoutLocking();
+
+ ASSERT(!m_context.source);
+ m_context = {
+ adoptGRef(g_timeout_source_new_seconds(delay.count())),
+ adoptGRef(g_cancellable_new()),
+ nullptr, // socketCancellable
+ nullptr, // voidCallback
+ WTF::move(function),
+ nullptr, // socketCallback
+ WTF::move(destroyFunction)
+ };
</ins><span class="cx"> scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void GMainLoopSource::voidCallback()
</span><span class="cx"> {
</span><del>- if (!m_source)
</del><ins>+ Context context;
+
+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return;
+
+ context = WTF::move(m_context);
+
+ ASSERT(context.voidCallback);
+ ASSERT(m_status == Scheduled);
+ m_status = Dispatching;
+
+ m_cancellable = context.cancellable;
+ }
+
+ context.voidCallback();
+
+ if (g_cancellable_is_cancelled(context.cancellable.get())) {
+ context.destroySource();
</ins><span class="cx"> return;
</span><ins>+ }
</ins><span class="cx">
</span><del>- ASSERT(m_voidCallback);
- ASSERT(m_status == Scheduled);
- m_status = Dispatched;
</del><ins>+ bool shouldSelfDestruct = false;
+ {
+ GMutexLocker locker(m_mutex);
+ m_status = Ready;
+ m_cancellable = nullptr;
+ shouldSelfDestruct = m_deleteOnDestroy == DeleteOnDestroy;
+ }
</ins><span class="cx">
</span><del>- GSource* source = m_source.get();
- m_voidCallback();
- if (source == m_source.get())
- destroy();
</del><ins>+ context.destroySource();
+ if (shouldSelfDestruct)
+ delete this;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> bool GMainLoopSource::boolCallback()
</span><span class="cx"> {
</span><del>- if (!m_source)
- return false;
</del><ins>+ Context context;
</ins><span class="cx">
</span><del>- ASSERT(m_boolCallback);
- ASSERT(m_status == Scheduled || m_status == Dispatched);
- m_status = Dispatched;
</del><ins>+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return Stop;
</ins><span class="cx">
</span><del>- GSource* source = m_source.get();
- bool retval = m_boolCallback();
- if (!retval && source == m_source.get())
- destroy();
</del><ins>+ context = WTF::move(m_context);
</ins><span class="cx">
</span><ins>+ ASSERT(context.boolCallback);
+ ASSERT(m_status == Scheduled || m_status == Dispatching);
+ m_status = Dispatching;
+
+ m_cancellable = context.cancellable;
+ }
+
+ bool retval = context.boolCallback();
+
+ if (g_cancellable_is_cancelled(context.cancellable.get())) {
+ context.destroySource();
+ return Stop;
+ }
+
+ bool shouldSelfDestruct = false;
+ {
+ GMutexLocker locker(m_mutex);
+ m_cancellable = nullptr;
+ shouldSelfDestruct = m_deleteOnDestroy == DeleteOnDestroy;
+
+ // m_status should reflect whether the GMainLoopSource has been rescheduled during dispatch.
+ ASSERT((!m_context.source && m_status == Dispatching) || m_status == Scheduled);
+ if (retval && !m_context.source)
+ m_context = WTF::move(context);
+ else if (!retval)
+ m_status = Ready;
+ }
+
+ if (context.source) {
+ context.destroySource();
+ if (shouldSelfDestruct)
+ delete this;
+ }
+
</ins><span class="cx"> return retval;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> bool GMainLoopSource::socketCallback(GIOCondition condition)
</span><span class="cx"> {
</span><del>- if (!m_source)
- return false;
</del><ins>+ Context context;
</ins><span class="cx">
</span><del>- ASSERT(m_socketCallback);
- ASSERT(m_status == Scheduled || m_status == Dispatched);
- m_status = Dispatched;
</del><ins>+ {
+ GMutexLocker locker(m_mutex);
+ if (!m_context.source)
+ return Stop;
</ins><span class="cx">
</span><del>- if (g_cancellable_is_cancelled(m_cancellable.get())) {
- destroy();
- return false;
</del><ins>+ context = WTF::move(m_context);
+
+ ASSERT(context.socketCallback);
+ ASSERT(m_status == Scheduled || m_status == Dispatching);
+ m_status = Dispatching;
+
+ m_cancellable = context.cancellable;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- GSource* source = m_source.get();
- bool retval = m_socketCallback(condition);
- if (!retval && source == m_source.get())
- destroy();
</del><ins>+ if (g_cancellable_is_cancelled(context.socketCancellable.get())) {
+ context.destroySource();
+ return Stop;
+ }
</ins><span class="cx">
</span><del>- return retval;
-}
</del><ins>+ bool retval = context.socketCallback(condition);
</ins><span class="cx">
</span><del>-void GMainLoopSource::destroy()
-{
- auto destroyCallback = WTF::move(m_destroyCallback);
- auto deleteOnDestroy = m_deleteOnDestroy;
- reset();
- if (destroyCallback)
- destroyCallback();
</del><ins>+ if (g_cancellable_is_cancelled(context.cancellable.get())) {
+ context.destroySource();
+ return Stop;
+ }
</ins><span class="cx">
</span><del>- if (deleteOnDestroy == DoNotDeleteOnDestroy)
- return;
</del><ins>+ {
+ GMutexLocker locker(m_mutex);
+ m_cancellable = nullptr;
</ins><span class="cx">
</span><del>- delete this;
</del><ins>+ // m_status should reflect whether the GMainLoopSource has been rescheduled during dispatch.
+ ASSERT((!m_context.source && m_status == Dispatching) || m_status == Scheduled);
+
+ if (retval && !m_context.source)
+ m_context = WTF::move(context);
+ else if (!retval)
+ m_status = Ready;
+ }
+
+ if (context.source)
+ context.destroySource();
+
+ return retval;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> gboolean GMainLoopSource::voidSourceCallback(GMainLoopSource* source)
</span><span class="lines">@@ -266,6 +410,13 @@
</span><span class="cx"> return source->socketCallback(condition) == Continue;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+void GMainLoopSource::Context::destroySource()
+{
+ g_source_destroy(source.get());
+ if (destroyCallback)
+ destroyCallback();
+}
+
</ins><span class="cx"> } // namespace WTF
</span><span class="cx">
</span><span class="cx"> #endif // USE(GLIB)
</span></span></pre></div>
<a id="trunkSourceWTFwtfgobjectGMainLoopSourceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/wtf/gobject/GMainLoopSource.h (173719 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/wtf/gobject/GMainLoopSource.h        2014-09-18 11:16:23 UTC (rev 173719)
+++ trunk/Source/WTF/wtf/gobject/GMainLoopSource.h        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> #include <wtf/gobject/GRefPtr.h>
</span><span class="cx">
</span><span class="cx"> typedef struct _GSocket GSocket;
</span><ins>+typedef union _GMutex GMutex;
</ins><span class="cx">
</span><span class="cx"> namespace WTF {
</span><span class="cx">
</span><span class="lines">@@ -63,14 +64,15 @@
</span><span class="cx"> enum DeleteOnDestroyType { DeleteOnDestroy, DoNotDeleteOnDestroy };
</span><span class="cx"> GMainLoopSource(DeleteOnDestroyType);
</span><span class="cx">
</span><del>- enum Status { Ready, Scheduled, Dispatched };
</del><ins>+ enum Status { Ready, Scheduled, Dispatching };
</ins><span class="cx">
</span><del>- void reset();
</del><ins>+ void cancelWithoutLocking();
</ins><span class="cx"> void scheduleIdleSource(const char* name, GSourceFunc, int priority, GMainContext*);
</span><span class="cx"> void scheduleTimeoutSource(const char* name, GSourceFunc, int priority, GMainContext*);
</span><span class="cx"> void voidCallback();
</span><span class="cx"> bool boolCallback();
</span><span class="cx"> bool socketCallback(GIOCondition);
</span><ins>+
</ins><span class="cx"> void destroy();
</span><span class="cx">
</span><span class="cx"> static gboolean voidSourceCallback(GMainLoopSource*);
</span><span class="lines">@@ -79,12 +81,24 @@
</span><span class="cx">
</span><span class="cx"> DeleteOnDestroyType m_deleteOnDestroy;
</span><span class="cx"> Status m_status;
</span><del>- GRefPtr<GSource> m_source;
</del><ins>+ GMutex m_mutex;
</ins><span class="cx"> GRefPtr<GCancellable> m_cancellable;
</span><del>- std::function<void ()> m_voidCallback;
- std::function<bool ()> m_boolCallback;
- std::function<bool (GIOCondition)> m_socketCallback;
- std::function<void ()> m_destroyCallback;
</del><ins>+
+ struct Context {
+ Context() = default;
+ Context(Context&&) = default;
+ Context& operator=(Context&&) = default;
+
+ void destroySource();
+
+ GRefPtr<GSource> source;
+ GRefPtr<GCancellable> cancellable;
+ GRefPtr<GCancellable> socketCancellable;
+ std::function<void ()> voidCallback;
+ std::function<bool ()> boolCallback;
+ std::function<bool (GIOCondition)> socketCallback;
+ std::function<void ()> destroyCallback;
+ } m_context;
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> } // namespace WTF
</span></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (173719 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog        2014-09-18 11:16:23 UTC (rev 173719)
+++ trunk/Tools/ChangeLog        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -1,3 +1,37 @@
</span><ins>+2014-09-18 Zan Dobersek <zdobersek@igalia.com>
+
+ GMainLoopSource is exposed to race conditions
+ https://bugs.webkit.org/show_bug.cgi?id=135800
+
+ Reviewed by Carlos Garcia Campos.
+
+ Add unit tests for GMainLoopSource.
+
+ The tests check correct behavior of GMainLoopSource in various conditions --
+ from the most simple rescheduling to rescheduling during dispatch, cancelling
+ or destroying the GMainLoopSource during dispatch, proper destroy callback
+ dispatching etc.
+
+ Scheduling both void (one-time) and bool (repeatable) callbacks is tested.
+ State of the GMainLoopSource object (either ready, sheduled or active) is
+ thoroughly tested throughout the lifetime of that object.
+
+ Still missing are tests for socket callbacks, which are a bit trickier because
+ they rely on a GSocket object. The delete-on-destroy GMainLoopSource objects
+ are also not tested thoroughly, simply because it is at the moment impossible
+ to test that the objects are actually destroyed when the corresponding source
+ is finally deleted.
+
+ * TestWebKitAPI/PlatformGTK.cmake:
+ * TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp: Added.
+ (TestWebKitAPI::GMainLoopSourceTest::GMainLoopSourceTest):
+ (TestWebKitAPI::GMainLoopSourceTest::~GMainLoopSourceTest):
+ (TestWebKitAPI::GMainLoopSourceTest::runLoop):
+ (TestWebKitAPI::GMainLoopSourceTest::delayedFinish):
+ (TestWebKitAPI::GMainLoopSourceTest::finish):
+ (TestWebKitAPI::GMainLoopSourceTest::source):
+ (TestWebKitAPI::TEST):
+
</ins><span class="cx"> 2014-09-17 Ryuan Choi <ryuan.choi@gmail.com>
</span><span class="cx">
</span><span class="cx"> Unreviewed, Update my email in contributors.json
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPIPlatformGTKcmake"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/PlatformGTK.cmake (173719 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/PlatformGTK.cmake        2014-09-18 11:16:23 UTC (rev 173719)
+++ trunk/Tools/TestWebKitAPI/PlatformGTK.cmake        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -136,5 +136,6 @@
</span><span class="cx"> set_target_properties(TestWebCore PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TESTWEBKITAPI_RUNTIME_OUTPUT_DIRECTORY}/WebCore)
</span><span class="cx">
</span><span class="cx"> list(APPEND TestWTF_SOURCES
</span><ins>+ ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GMainLoopSource.cpp
</ins><span class="cx"> ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GUniquePtr.cpp
</span><span class="cx"> )
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWTFgobjectGMainLoopSourcecpp"></a>
<div class="addfile"><h4>Added: trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp (0 => 173720)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp         (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp        2014-09-18 13:06:59 UTC (rev 173720)
</span><span class="lines">@@ -0,0 +1,489 @@
</span><ins>+/*
+ * 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 delayedFinish()
+ {
+ g_timeout_add(250,
+ [](gpointer data) {
+ GMainLoopSourceTest& test = *static_cast<GMainLoopSourceTest*>(data);
+ test.finish();
+ return G_SOURCE_REMOVE;
+ }, this);
+ }
+
+ 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;
+
+ EXPECT_TRUE(!context.test.source().isActive());
+
+ 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;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_FALSE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+TEST(WTF_GMainLoopSource, ReentrantRescheduling)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool finishedFirstTask = false;
+ bool finishedSecondTask = false;
+ } context;
+
+ EXPECT_TRUE(!context.test.source().isActive());
+
+ context.test.source().schedule("[Test] FirstTask", [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ EXPECT_TRUE(context.finishedFirstTask);
+
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.finishedFirstTask = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_TRUE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+TEST(WTF_GMainLoopSource, ReschedulingFromDifferentThread)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool finishedFirstTask;
+ bool finishedSecondTask;
+ } context;
+
+ EXPECT_TRUE(!context.test.source().isActive());
+
+ context.test.source().schedule("[Test] FirstTask", [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+
+ g_usleep(1 * G_USEC_PER_SEC);
+ context.finishedFirstTask = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ GThread* helperThread = g_thread_new(nullptr, [](gpointer data) -> gpointer {
+ g_usleep(0.25 * G_USEC_PER_SEC);
+
+ TestingContext& context = *static_cast<TestingContext*>(data);
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ EXPECT_FALSE(context.finishedFirstTask);
+
+ context.test.source().schedule("[Test] SecondTask", [&] {
+ EXPECT_TRUE(context.finishedFirstTask);
+
+ context.finishedSecondTask = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ g_thread_exit(nullptr);
+ return nullptr;
+ }, &context);
+
+ context.test.runLoop();
+ g_thread_unref(helperThread);
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_TRUE(context.finishedFirstTask);
+ EXPECT_TRUE(context.finishedSecondTask);
+}
+
+TEST(WTF_GMainLoopSource, DestructionDuringDispatch)
+{
+ // This is just a raw test that ensures deleting the GMainLoopSource object during
+ // dispatch does not cause problems. This test succeeds if it doesn't crash.
+
+ GMainLoopSource* source;
+ GMainLoop* loop = g_main_loop_new(nullptr, TRUE);
+
+ source = new GMainLoopSource;
+ source->schedule("[Test] DestroySourceTask", [&] {
+ delete source;
+ g_main_loop_quit(loop);
+ });
+ g_main_loop_run(loop);
+
+ source = new GMainLoopSource;
+ source->schedule("[Test] DestroySourceTask", std::function<bool ()>([&] {
+ delete source;
+ g_main_loop_quit(loop);
+ return false;
+ }));
+ g_main_loop_run(loop);
+
+ g_main_loop_unref(loop);
+}
+
+TEST(WTF_GMainLoopSource, CancelRepeatingSourceDuringDispatch)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ unsigned callCount = 0;
+ } context;
+
+ EXPECT_TRUE(!context.test.source().isActive());
+
+ context.test.source().schedule("[Test] RepeatingTask",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+
+ context.callCount++;
+ if (context.callCount == 3)
+ context.test.source().cancel();
+ return true;
+ }));
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_EQ(3, context.callCount);
+}
+
+TEST(WTF_GMainLoopSource, BasicDestroyCallbacks)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ bool callbackCalled = false;
+ bool destroyCallbackCalled = false;
+ };
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] DestroyCallback",
+ [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.callbackCalled = true;
+ }, G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.destroyCallbackCalled = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_TRUE(context.callbackCalled);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] DestroyCallback",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.callbackCalled = true;
+ return false;
+ }), G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.destroyCallbackCalled = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_TRUE(context.callbackCalled);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+}
+
+TEST(WTF_GMainLoopSource, DestroyCallbacksAfterCancellingDuringDispatch)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ unsigned callbackCallCount= 0;
+ bool destroyCallbackCalled = false;
+ };
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] DestroyCallback",
+ [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.callbackCallCount++;
+ context.test.source().cancel();
+ }, G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.destroyCallbackCalled = true;
+ context.test.finish();
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_EQ(1, context.callbackCallCount);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] DestroyCallback",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.callbackCallCount++;
+ if (context.callbackCallCount == 3)
+ context.test.source().cancel();
+ return true;
+ }), G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.destroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_EQ(3, context.callbackCallCount);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+}
+
+TEST(WTF_GMainLoopSource, DestroyCallbacksAfterReschedulingDuringDispatch)
+{
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ unsigned firstCallbackCallCount = 0;
+ bool firstDestroyCallbackCalled = false;
+ unsigned secondCallbackCallCount = 0;
+ bool secondDestroyCallbackCalled = false;
+ };
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] BaseCallback",
+ [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.firstCallbackCallCount++;
+ context.test.source().schedule("[Test] ReschedulingCallback",
+ [&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.secondCallbackCallCount++;
+ }, G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.secondDestroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+ }, G_PRIORITY_DEFAULT,
+ [&] {
+ // At this point the GMainLoopSource has been rescheduled, ergo the Scheduled status.
+ EXPECT_TRUE(context.test.source().isScheduled());
+ context.firstDestroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_EQ(1, context.firstCallbackCallCount);
+ EXPECT_TRUE(context.firstDestroyCallbackCalled);
+ EXPECT_EQ(1, context.secondCallbackCallCount);
+ EXPECT_TRUE(context.secondDestroyCallbackCalled);
+ }
+
+ {
+ TestingContext context;
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.test.source().schedule("[Test] BaseCallback",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.firstCallbackCallCount++;
+ context.test.source().schedule("[Test] ReschedulingCallback",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(context.test.source().isActive() && !context.test.source().isScheduled());
+ context.secondCallbackCallCount++;
+ return context.secondCallbackCallCount != 3;
+ }), G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!context.test.source().isActive());
+ context.secondDestroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+ return true;
+ }), G_PRIORITY_DEFAULT,
+ [&] {
+ // At this point the GMainLoopSource has been rescheduled, ergo the Scheduled status.
+ EXPECT_TRUE(context.test.source().isScheduled());
+ context.firstDestroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(context.test.source().isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+
+ EXPECT_TRUE(!context.test.source().isActive());
+ EXPECT_EQ(1, context.firstCallbackCallCount);
+ EXPECT_TRUE(context.firstDestroyCallbackCalled);
+ EXPECT_EQ(3, context.secondCallbackCallCount);
+ EXPECT_TRUE(context.secondDestroyCallbackCalled);
+ }
+}
+
+TEST(WTF_GMainLoopSource, DeleteOnDestroySources)
+{
+ // Testing the delete-on-destroy sources is very limited. There's no good way
+ // of testing that the GMainLoopSource objects are deleted when their GSource
+ // is destroyed, and the socket callbacks shouldn't be scheduled on these types
+ // of GMainLoopSources (as we aggressively assert to prevent that).
+
+ struct TestingContext {
+ GMainLoopSourceTest test;
+ unsigned callbackCallCount = 0;
+ bool destroyCallbackCalled = false;
+ } context;
+
+ {
+ TestingContext context;
+
+ // We take a reference to the GMainLoopSource just to perform additional
+ // tests on its status. We shouldn't use the reference after the main loop
+ // exists since at that point the GMainLoopSource will be destroyed and
+ // the reference pointing to an invalid piece of memory.
+ GMainLoopSource& source = GMainLoopSource::createAndDeleteOnDestroy();
+ EXPECT_TRUE(!source.isActive());
+ source.schedule("[Test] DeleteOnDestroy",
+ [&] {
+ EXPECT_TRUE(source.isActive() && !source.isScheduled());
+ context.callbackCallCount++;
+ }, G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!source.isActive());
+ EXPECT_FALSE(context.destroyCallbackCalled);
+ context.destroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(source.isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+ EXPECT_EQ(1, context.callbackCallCount);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+
+ {
+ TestingContext context;
+
+ // As in the previous scope, we need a reference to the GMainLoopSource.
+ GMainLoopSource& source = GMainLoopSource::createAndDeleteOnDestroy();
+ EXPECT_TRUE(!source.isActive());
+ source.schedule("[Test] DeleteOnDestroy",
+ std::function<bool ()>([&] {
+ EXPECT_TRUE(source.isActive() && !source.isScheduled());
+ context.callbackCallCount++;
+ return context.callbackCallCount != 3;
+ }), G_PRIORITY_DEFAULT,
+ [&] {
+ EXPECT_TRUE(!source.isActive());
+ EXPECT_FALSE(context.destroyCallbackCalled);
+ context.destroyCallbackCalled = true;
+ });
+ EXPECT_TRUE(source.isScheduled());
+
+ context.test.delayedFinish();
+ context.test.runLoop();
+ EXPECT_EQ(3, context.callbackCallCount);
+ EXPECT_TRUE(context.destroyCallbackCalled);
+ }
+}
+
+} // namespace TestWebKitAPI
</ins></span></pre>
</div>
</div>
</body>
</html>