Changeset 173201 in webkit
- Timestamp:
- Sep 3, 2014 12:26:52 AM (10 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WTF/ChangeLog
r173192 r173201 1 2014-09-03 Zan Dobersek <zdobersek@igalia.com> 2 3 GMainLoopSource is exposed to race conditions 4 https://bugs.webkit.org/show_bug.cgi?id=135800 5 6 Reviewed by Carlos Garcia Campos. 7 8 GMainLoopSource objects can be dispatching tasks on one thread 9 while having a new task scheduled on a different thread. This 10 can for instance occur in WebKitVideoSink, where the timeout 11 callback can be called on main thread while at the same time 12 it is being rescheduled on a different thread (created through 13 GStreamer). 14 15 The initial solution is to use GMutex to prevent parallel data 16 access from different threads. In the future I plan to add better 17 assertions, some meaningful comments and look at the possibility 18 of creating thread-specific GMainLoopSource objects that wouldn't 19 require the use of GMutex. 20 21 GSource, GCancellable and std::function<> objects are now packed 22 into an internal Context structure. Using the C++11 move semantics 23 it's simple to, at the time of dispatch, move the current context 24 out of the GMainLoopSource object in case the dispatch causes a 25 rescheduling on that same object. 26 27 All the schedule*() methods and the cancelInternal() method callers 28 now lock the GMutex to ensure no one else is accessing the data at 29 that moment. Similar goes for the dispatch methods, but those do 30 the dispatch and possible destruction duties with the mutex unlocked. 31 The dispatch can cause rescheduling on the same GMainLoopSource object, 32 which must not be done with a locked mutex. 33 34 * wtf/gobject/GMainLoopSource.cpp: 35 (WTF::GMainLoopSource::GMainLoopSource): 36 (WTF::GMainLoopSource::~GMainLoopSource): 37 (WTF::GMainLoopSource::cancel): 38 (WTF::GMainLoopSource::cancelInternal): 39 (WTF::GMainLoopSource::scheduleIdleSource): 40 (WTF::GMainLoopSource::schedule): 41 (WTF::GMainLoopSource::scheduleTimeoutSource): 42 (WTF::GMainLoopSource::scheduleAfterDelay): 43 (WTF::GMainLoopSource::voidCallback): 44 (WTF::GMainLoopSource::boolCallback): 45 (WTF::GMainLoopSource::socketCallback): 46 (WTF::GMainLoopSource::destroy): 47 (WTF::GMainLoopSource::reset): Deleted. 48 * wtf/gobject/GMainLoopSource.h: 49 1 50 2014-09-02 Daniel Bates <dabates@apple.com> 2 51 -
trunk/Source/WTF/wtf/gobject/GMainLoopSource.cpp
r170774 r173201 29 29 30 30 #include "GMainLoopSource.h" 31 32 31 #include <gio/gio.h> 32 #include <wtf/gobject/GMutexLocker.h> 33 33 34 34 namespace WTF { … … 43 43 , m_status(Ready) 44 44 { 45 g_mutex_init(&m_mutex); 45 46 } 46 47 … … 49 50 , m_status(Ready) 50 51 { 52 g_mutex_init(&m_mutex); 51 53 } 52 54 … … 54 56 { 55 57 cancel(); 58 g_mutex_clear(&m_mutex); 56 59 } 57 60 … … 68 71 void GMainLoopSource::cancel() 69 72 { 70 if (!m_source) 73 GMutexLocker locker(m_mutex); 74 cancelWithoutLocking(); 75 } 76 77 void GMainLoopSource::cancelWithoutLocking() 78 { 79 if (!m_context.source) { 80 m_status = Ready; 71 81 return; 72 73 GRefPtr<GSource> source; 74 m_source.swap(source); 75 76 if (m_cancellable) 77 g_cancellable_cancel(m_cancellable.get()); 78 g_source_destroy(source.get()); 79 destroy(); 80 } 81 82 void GMainLoopSource::reset() 83 { 84 m_status = Ready; 85 m_source = nullptr; 86 m_cancellable = nullptr; 87 m_voidCallback = nullptr; 88 m_boolCallback = nullptr; 89 m_destroyCallback = nullptr; 82 } 83 84 Context context = WTF::move(m_context); 85 86 if (context.cancellable) 87 g_cancellable_cancel(context.cancellable.get()); 88 89 g_source_destroy(context.source.get()); 90 destroy(context.destroyCallback); 90 91 } 91 92 … … 95 96 m_status = Scheduled; 96 97 97 m_ source = adoptGRef(g_idle_source_new());98 g_source_set_name(m_ source.get(), name);98 m_context.source = adoptGRef(g_idle_source_new()); 99 g_source_set_name(m_context.source.get(), name); 99 100 if (priority != G_PRIORITY_DEFAULT_IDLE) 100 g_source_set_priority(m_ source.get(), priority);101 g_source_set_callback(m_ source.get(), sourceFunction, this, nullptr);102 g_source_attach(m_ source.get(), context);101 g_source_set_priority(m_context.source.get(), priority); 102 g_source_set_callback(m_context.source.get(), sourceFunction, this, nullptr); 103 g_source_attach(m_context.source.get(), context); 103 104 } 104 105 105 106 void GMainLoopSource::schedule(const char* name, std::function<void ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context) 106 107 { 107 cancel(); 108 m_voidCallback = WTF::move(function); 109 m_destroyCallback = WTF::move(destroyFunction); 108 GMutexLocker locker(m_mutex); 109 cancelWithoutLocking(); 110 m_context.voidCallback = WTF::move(function); 111 m_context.destroyCallback = WTF::move(destroyFunction); 110 112 scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context); 111 113 } … … 113 115 void GMainLoopSource::schedule(const char* name, std::function<bool ()> function, int priority, std::function<void ()> destroyFunction, GMainContext* context) 114 116 { 115 cancel(); 116 m_boolCallback = WTF::move(function); 117 m_destroyCallback = WTF::move(destroyFunction); 117 GMutexLocker locker(m_mutex); 118 cancelWithoutLocking(); 119 m_context.boolCallback = WTF::move(function); 120 m_context.destroyCallback = WTF::move(destroyFunction); 118 121 scheduleIdleSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context); 119 122 } … … 121 124 void GMainLoopSource::schedule(const char* name, std::function<bool (GIOCondition)> function, GSocket* socket, GIOCondition condition, std::function<void ()> destroyFunction, GMainContext* context) 122 125 { 123 cancel(); 126 GMutexLocker locker(m_mutex); 127 cancelWithoutLocking(); 124 128 ASSERT(m_status == Ready); 125 129 m_status = Scheduled; 126 130 127 m_ socketCallback = WTF::move(function);128 m_ destroyCallback = WTF::move(destroyFunction);129 m_c ancellable = adoptGRef(g_cancellable_new());130 m_ source = adoptGRef(g_socket_create_source(socket, condition, m_cancellable.get()));131 g_source_set_name(m_ source.get(), name);132 g_source_set_callback(m_ source.get(), reinterpret_cast<GSourceFunc>(socketSourceCallback), this, nullptr);133 g_source_attach(m_ source.get(), context);131 m_context.socketCallback = WTF::move(function); 132 m_context.destroyCallback = WTF::move(destroyFunction); 133 m_context.cancellable = adoptGRef(g_cancellable_new()); 134 m_context.source = adoptGRef(g_socket_create_source(socket, condition, m_context.cancellable.get())); 135 g_source_set_name(m_context.source.get(), name); 136 g_source_set_callback(m_context.source.get(), reinterpret_cast<GSourceFunc>(socketSourceCallback), this, nullptr); 137 g_source_attach(m_context.source.get(), context); 134 138 } 135 139 … … 139 143 m_status = Scheduled; 140 144 141 ASSERT(m_ source);142 g_source_set_name(m_ source.get(), name);145 ASSERT(m_context.source); 146 g_source_set_name(m_context.source.get(), name); 143 147 if (priority != G_PRIORITY_DEFAULT) 144 g_source_set_priority(m_ source.get(), priority);145 g_source_set_callback(m_ source.get(), sourceFunction, this, nullptr);146 g_source_attach(m_ source.get(), context);148 g_source_set_priority(m_context.source.get(), priority); 149 g_source_set_callback(m_context.source.get(), sourceFunction, this, nullptr); 150 g_source_attach(m_context.source.get(), context); 147 151 } 148 152 149 153 void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context) 150 154 { 151 cancel(); 152 m_source = adoptGRef(g_timeout_source_new(delay.count())); 153 m_voidCallback = WTF::move(function); 154 m_destroyCallback = WTF::move(destroyFunction); 155 GMutexLocker locker(m_mutex); 156 cancelWithoutLocking(); 157 m_context.source = adoptGRef(g_timeout_source_new(delay.count())); 158 m_context.voidCallback = WTF::move(function); 159 m_context.destroyCallback = WTF::move(destroyFunction); 155 160 scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context); 156 161 } … … 158 163 void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::milliseconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context) 159 164 { 160 cancel(); 161 m_source = adoptGRef(g_timeout_source_new(delay.count())); 162 m_boolCallback = WTF::move(function); 163 m_destroyCallback = WTF::move(destroyFunction); 165 GMutexLocker locker(m_mutex); 166 cancelWithoutLocking(); 167 m_context.source = adoptGRef(g_timeout_source_new(delay.count())); 168 m_context.boolCallback = WTF::move(function); 169 m_context.destroyCallback = WTF::move(destroyFunction); 164 170 scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context); 165 171 } … … 167 173 void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<void ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context) 168 174 { 169 cancel(); 170 m_source = adoptGRef(g_timeout_source_new_seconds(delay.count())); 171 m_voidCallback = WTF::move(function); 172 m_destroyCallback = WTF::move(destroyFunction); 175 GMutexLocker locker(m_mutex); 176 cancelWithoutLocking(); 177 m_context.source = adoptGRef(g_timeout_source_new_seconds(delay.count())); 178 m_context.voidCallback = WTF::move(function); 179 m_context.destroyCallback = WTF::move(destroyFunction); 173 180 scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(voidSourceCallback), priority, context); 174 181 } … … 176 183 void GMainLoopSource::scheduleAfterDelay(const char* name, std::function<bool ()> function, std::chrono::seconds delay, int priority, std::function<void ()> destroyFunction, GMainContext* context) 177 184 { 178 cancel(); 179 m_source = adoptGRef(g_timeout_source_new_seconds(delay.count())); 180 m_boolCallback = WTF::move(function); 181 m_destroyCallback = WTF::move(destroyFunction); 185 GMutexLocker locker(m_mutex); 186 cancelWithoutLocking(); 187 m_context.source = adoptGRef(g_timeout_source_new_seconds(delay.count())); 188 m_context.boolCallback = WTF::move(function); 189 m_context.destroyCallback = WTF::move(destroyFunction); 182 190 scheduleTimeoutSource(name, reinterpret_cast<GSourceFunc>(boolSourceCallback), priority, context); 183 191 } … … 185 193 void GMainLoopSource::voidCallback() 186 194 { 187 if (!m_source) 188 return; 189 190 ASSERT(m_voidCallback); 191 ASSERT(m_status == Scheduled); 192 m_status = Dispatched; 193 194 GSource* source = m_source.get(); 195 m_voidCallback(); 196 if (source == m_source.get()) 197 destroy(); 195 Context context; 196 197 { 198 GMutexLocker locker(m_mutex); 199 if (!m_context.source) 200 return; 201 202 context = WTF::move(m_context); 203 204 ASSERT(context.voidCallback); 205 ASSERT(m_status == Scheduled); 206 m_status = Dispatched; 207 } 208 209 context.voidCallback(); 210 211 bool shouldDestroy = false; 212 { 213 GMutexLocker locker(m_mutex); 214 shouldDestroy = !m_context.source; 215 } 216 217 if (shouldDestroy) 218 destroy(context.destroyCallback); 198 219 } 199 220 200 221 bool GMainLoopSource::boolCallback() 201 222 { 202 if (!m_source) 203 return false; 204 205 ASSERT(m_boolCallback); 206 ASSERT(m_status == Scheduled || m_status == Dispatched); 207 m_status = Dispatched; 208 209 GSource* source = m_source.get(); 210 bool retval = m_boolCallback(); 211 if (!retval && source == m_source.get()) 212 destroy(); 213 223 Context context; 224 225 { 226 GMutexLocker locker(m_mutex); 227 if (!m_context.source) 228 return Stop; 229 230 context = WTF::move(m_context); 231 232 ASSERT(context.boolCallback); 233 ASSERT(m_status == Scheduled || m_status == Dispatched); 234 m_status = Dispatched; 235 } 236 237 bool retval = context.boolCallback(); 238 239 bool shouldDestroy = false; 240 { 241 GMutexLocker locker(m_mutex); 242 if (retval && !m_context.source) 243 m_context = WTF::move(context); 244 else 245 shouldDestroy = !m_context.source; 246 } 247 248 if (shouldDestroy) 249 destroy(context.destroyCallback); 214 250 return retval; 215 251 } … … 217 253 bool GMainLoopSource::socketCallback(GIOCondition condition) 218 254 { 219 if (!m_source) 220 return false; 221 222 ASSERT(m_socketCallback); 223 ASSERT(m_status == Scheduled || m_status == Dispatched); 224 m_status = Dispatched; 225 226 if (g_cancellable_is_cancelled(m_cancellable.get())) { 227 destroy(); 228 return false; 229 } 230 231 GSource* source = m_source.get(); 232 bool retval = m_socketCallback(condition); 233 if (!retval && source == m_source.get()) 234 destroy(); 235 255 Context context; 256 257 { 258 GMutexLocker locker(m_mutex); 259 if (!m_context.source) 260 return Stop; 261 262 context = WTF::move(m_context); 263 264 ASSERT(context.socketCallback); 265 ASSERT(m_status == Scheduled || m_status == Dispatched); 266 m_status = Dispatched; 267 } 268 269 if (g_cancellable_is_cancelled(context.cancellable.get())) { 270 destroy(context.destroyCallback); 271 return Stop; 272 } 273 274 bool retval = context.socketCallback(condition); 275 276 bool shouldDestroy = false; 277 { 278 GMutexLocker locker(m_mutex); 279 if (retval && !m_context.source) 280 m_context = WTF::move(context); 281 else 282 shouldDestroy = !m_context.source; 283 } 284 285 if (shouldDestroy) 286 destroy(context.destroyCallback); 236 287 return retval; 237 288 } 238 289 239 void GMainLoopSource::destroy() 240 { 241 auto destroyCallback = WTF::move(m_destroyCallback); 242 auto deleteOnDestroy = m_deleteOnDestroy; 243 reset(); 290 void GMainLoopSource::destroy(const std::function<void ()>& destroyCallback) 291 { 292 // Nothing should be scheduled on this object at this point. 293 ASSERT(!m_context.source); 294 m_status = Ready; 295 DeleteOnDestroyType deleteOnDestroy = m_deleteOnDestroy; 296 244 297 if (destroyCallback) 245 298 destroyCallback(); -
trunk/Source/WTF/wtf/gobject/GMainLoopSource.h
r166636 r173201 33 33 34 34 typedef struct _GSocket GSocket; 35 typedef union _GMutex GMutex; 35 36 36 37 namespace WTF { … … 66 67 enum Status { Ready, Scheduled, Dispatched }; 67 68 68 void reset();69 void cancelWithoutLocking(); 69 70 void scheduleIdleSource(const char* name, GSourceFunc, int priority, GMainContext*); 70 71 void scheduleTimeoutSource(const char* name, GSourceFunc, int priority, GMainContext*); … … 72 73 bool boolCallback(); 73 74 bool socketCallback(GIOCondition); 74 void destroy( );75 void destroy(const std::function<void ()>&); 75 76 76 77 static gboolean voidSourceCallback(GMainLoopSource*); … … 80 81 DeleteOnDestroyType m_deleteOnDestroy; 81 82 Status m_status; 82 GRefPtr<GSource> m_source; 83 GRefPtr<GCancellable> m_cancellable; 84 std::function<void ()> m_voidCallback; 85 std::function<bool ()> m_boolCallback; 86 std::function<bool (GIOCondition)> m_socketCallback; 87 std::function<void ()> m_destroyCallback; 83 GMutex m_mutex; 84 85 struct Context { 86 Context() = default; 87 Context(Context&&) = default; 88 Context& operator=(Context&&) = default; 89 90 GRefPtr<GSource> source; 91 GRefPtr<GCancellable> cancellable; 92 std::function<void ()> voidCallback; 93 std::function<bool ()> boolCallback; 94 std::function<bool (GIOCondition)> socketCallback; 95 std::function<void ()> destroyCallback; 96 } m_context; 88 97 }; 89 98 -
trunk/Tools/ChangeLog
r173187 r173201 1 2014-09-03 Zan Dobersek <zdobersek@igalia.com> 2 3 GMainLoopSource is exposed to race conditions 4 https://bugs.webkit.org/show_bug.cgi?id=135800 5 6 Reviewed by Carlos Garcia Campos. 7 8 Add a unit test for GMainLoopSource that tests different 9 types of rescheduling tasks on already-active sources. 10 11 * TestWebKitAPI/PlatformGTK.cmake: 12 * TestWebKitAPI/Tests/WTF/gobject/GMainLoopSource.cpp: Added. 13 (GMainLoopSourceTest::GMainLoopSourceTest): 14 (GMainLoopSourceTest::~GMainLoopSourceTest): 15 (GMainLoopSourceTest::runLoop): 16 (GMainLoopSourceTest::finish): 17 (GMainLoopSourceTest::source): 18 (testGMainLoopSourceBasicRescheduling): 19 (testGMainLoopSourceReentrantRescheduling): 20 (testGMainLoopSourceDifferentThreadRescheduling): 21 (beforeAll): 22 (afterAll): 23 (TestWebKitAPI::GMainLoopSourceTest::GMainLoopSourceTest): 24 (TestWebKitAPI::GMainLoopSourceTest::~GMainLoopSourceTest): 25 (TestWebKitAPI::GMainLoopSourceTest::runLoop): 26 (TestWebKitAPI::GMainLoopSourceTest::finish): 27 (TestWebKitAPI::GMainLoopSourceTest::source): 28 (TestWebKitAPI::TEST): 29 1 30 2014-09-02 Simon Fraser <simon.fraser@apple.com> 2 31 -
trunk/Tools/TestWebKitAPI/PlatformGTK.cmake
r172586 r173201 137 137 138 138 list(APPEND TestWTF_SOURCES 139 ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GMainLoopSource.cpp 139 140 ${TESTWEBKITAPI_DIR}/Tests/WTF/gobject/GUniquePtr.cpp 140 141 )
Note: See TracChangeset
for help on using the changeset viewer.