[GStreamer] Adaptive streaming issues
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Jun 2016 06:33:57 +0000 (06:33 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Jun 2016 06:33:57 +0000 (06:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=144040

Reviewed by Philippe Normand.

There are multiple deadlocks in the web process when HLS content is loaded by GStreamer. It happens because gst
is using several threads to download manifest, fragments, monitor the downloads, etc. To download the fragments
and manifest it always creates the source element in a separate thread, something that is not actually expected
to happen in WebKit source element. Our source element is always scheduling tasks (start, stop, need-data,
enough-data and seek) to the main thread, and those downloads that use the ResourceHandleStreamingClient
(there's no player associated) also happen in the main thread, because libsoup calls all its async callbacks in
the main thread. So, the result is that it can happen that we end up blocking the main thread in a lock until
the download finishes, but the download never finishes because tasks are scheduled in the main thread that is
blocked in a lock. This can be prevented by always using a secondary thread for downloads made by
ResourceHandleStreamingClient, using its own run loop with a different GMainContext so that libsoup sends
callbacks to the right thread. We also had to refactor the tasks a bit, leaving the thread safe parts to be run
in the calling thread always, and only scheduling to the main thread in case of not using
ResourceHandleStreamingClient and only for the non thread safe parts.
This patch also includes r200455 that was rolled out, but it was a perfectly valid workaround for GST bug.

* platform/graphics/gstreamer/GRefPtrGStreamer.cpp:
(WTF::ensureGRef): Consume the floating ref if needed.
* platform/graphics/gstreamer/GRefPtrGStreamer.h:
* platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp:
(webkit_web_src_init): Check if object is being created in the main thread.
(webKitWebSrcStop): Stop the media resource loader in the main thread and the resource handle streaming in the
current thread.
(webKitWebSrcStart): Start the media resource loader in the main thread and the resource handle streaming in
the current thread.
(webKitWebSrcChangeState): Call webKitWebSrcStart and webKitWebSrcStop in the current thread.
(webKitWebSrcNeedData): Update status in the current thread and notify the media resource loader in the main thread.
(webKitWebSrcEnoughData): Ditto.
(webKitWebSrcSeek): Ditto.
(webKitWebSrcSetMediaPlayer): Add an assert to ensure that source elements used by WebKit are always created in
the main thread.
(ResourceHandleStreamingClient::ResourceHandleStreamingClient): Use a secondary thread to do the download.
(ResourceHandleStreamingClient::~ResourceHandleStreamingClient): Stop the secondary thread.
(ResourceHandleStreamingClient::setDefersLoading): Notify the secondary thread.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@202615 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h
Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp

index 3a46a61..dde79d6 100644 (file)
@@ -1,3 +1,44 @@
+2016-06-28  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GStreamer] Adaptive streaming issues
+        https://bugs.webkit.org/show_bug.cgi?id=144040
+
+        Reviewed by Philippe Normand.
+
+        There are multiple deadlocks in the web process when HLS content is loaded by GStreamer. It happens because gst
+        is using several threads to download manifest, fragments, monitor the downloads, etc. To download the fragments
+        and manifest it always creates the source element in a separate thread, something that is not actually expected
+        to happen in WebKit source element. Our source element is always scheduling tasks (start, stop, need-data,
+        enough-data and seek) to the main thread, and those downloads that use the ResourceHandleStreamingClient
+        (there's no player associated) also happen in the main thread, because libsoup calls all its async callbacks in
+        the main thread. So, the result is that it can happen that we end up blocking the main thread in a lock until
+        the download finishes, but the download never finishes because tasks are scheduled in the main thread that is
+        blocked in a lock. This can be prevented by always using a secondary thread for downloads made by
+        ResourceHandleStreamingClient, using its own run loop with a different GMainContext so that libsoup sends
+        callbacks to the right thread. We also had to refactor the tasks a bit, leaving the thread safe parts to be run
+        in the calling thread always, and only scheduling to the main thread in case of not using
+        ResourceHandleStreamingClient and only for the non thread safe parts.
+        This patch also includes r200455 that was rolled out, but it was a perfectly valid workaround for GST bug.
+
+        * platform/graphics/gstreamer/GRefPtrGStreamer.cpp:
+        (WTF::ensureGRef): Consume the floating ref if needed.
+        * platform/graphics/gstreamer/GRefPtrGStreamer.h:
+        * platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp:
+        (webkit_web_src_init): Check if object is being created in the main thread.
+        (webKitWebSrcStop): Stop the media resource loader in the main thread and the resource handle streaming in the
+        current thread.
+        (webKitWebSrcStart): Start the media resource loader in the main thread and the resource handle streaming in
+        the current thread.
+        (webKitWebSrcChangeState): Call webKitWebSrcStart and webKitWebSrcStop in the current thread.
+        (webKitWebSrcNeedData): Update status in the current thread and notify the media resource loader in the main thread.
+        (webKitWebSrcEnoughData): Ditto.
+        (webKitWebSrcSeek): Ditto.
+        (webKitWebSrcSetMediaPlayer): Add an assert to ensure that source elements used by WebKit are always created in
+        the main thread.
+        (ResourceHandleStreamingClient::ResourceHandleStreamingClient): Use a secondary thread to do the download.
+        (ResourceHandleStreamingClient::~ResourceHandleStreamingClient): Stop the secondary thread.
+        (ResourceHandleStreamingClient::setDefersLoading): Notify the secondary thread.
+
 2016-06-28  Youenn Fablet  <youennf@gmail.com>
 
         Remove ThreadableLoaderOptions origin
index 47113f6..2372243 100644 (file)
@@ -342,6 +342,16 @@ template <> GRefPtr<WebKitWebSrc> adoptGRef(WebKitWebSrc* ptr)
     return GRefPtr<WebKitWebSrc>(ptr, GRefPtrAdopt);
 }
 
+// This method is only available for WebKitWebSrc and should not be used for any other type.
+// This is only to work around a bug in GST where the URI downloader is not taking the ownership of WebKitWebSrc.
+// See https://bugs.webkit.org/show_bug.cgi?id=144040.
+GRefPtr<WebKitWebSrc> ensureGRef(WebKitWebSrc* ptr)
+{
+    if (ptr && g_object_is_floating(ptr))
+        gst_object_ref_sink(GST_OBJECT(ptr));
+    return GRefPtr<WebKitWebSrc>(ptr);
+}
+
 template <> WebKitWebSrc* refGPtr<WebKitWebSrc>(WebKitWebSrc* ptr)
 {
     if (ptr)
index 81e1e0d..1a336cc 100644 (file)
@@ -108,6 +108,7 @@ template<> WebKitVideoSink* refGPtr<WebKitVideoSink>(WebKitVideoSink* ptr);
 template<> void derefGPtr<WebKitVideoSink>(WebKitVideoSink* ptr);
 
 template<> GRefPtr<WebKitWebSrc> adoptGRef(WebKitWebSrc* ptr);
+GRefPtr<WebKitWebSrc> ensureGRef(WebKitWebSrc* ptr);
 template<> WebKitWebSrc* refGPtr<WebKitWebSrc>(WebKitWebSrc* ptr);
 template<> void derefGPtr<WebKitWebSrc>(WebKitWebSrc* ptr);
 
index 6dcf48d..04c5fa8 100644 (file)
@@ -83,7 +83,7 @@ class CachedResourceStreamingClient final : public PlatformMediaResourceClient,
 class ResourceHandleStreamingClient : public ResourceHandleClient, public StreamingClient {
     WTF_MAKE_NONCOPYABLE(ResourceHandleStreamingClient); WTF_MAKE_FAST_ALLOCATED;
     public:
-        ResourceHandleStreamingClient(WebKitWebSrc*, const ResourceRequest&);
+        ResourceHandleStreamingClient(WebKitWebSrc*, ResourceRequest&&);
         virtual ~ResourceHandleStreamingClient();
 
         // StreamingClient virtual methods.
@@ -104,6 +104,12 @@ class ResourceHandleStreamingClient : public ResourceHandleClient, public Stream
         void wasBlocked(ResourceHandle*) override;
         void cannotShowURL(ResourceHandle*) override;
 
+        ThreadIdentifier m_thread { 0 };
+        Lock m_initializeRunLoopConditionMutex;
+        Condition m_initializeRunLoopCondition;
+        RunLoop* m_runLoop { nullptr };
+        Lock m_terminateRunLoopConditionMutex;
+        Condition m_terminateRunLoopCondition;
         RefPtr<ResourceHandle> m_resource;
 };
 
@@ -129,18 +135,19 @@ struct _WebKitWebSrcPrivate {
 
     RefPtr<PlatformMediaResourceLoader> loader;
     RefPtr<PlatformMediaResource> resource;
-    ResourceHandleStreamingClient* client;
+    std::unique_ptr<ResourceHandleStreamingClient> client;
 
     bool didPassAccessControlCheck;
 
     guint64 offset;
     guint64 size;
     gboolean seekable;
-    gboolean paused;
+    bool paused;
     bool isSeeking;
 
     guint64 requestedOffset;
 
+    bool createdInMainThread;
     MainThreadNotifier<MainThreadSourceNotification> notifier;
     GRefPtr<GstBuffer> buffer;
 };
@@ -174,57 +181,20 @@ static gboolean webKitWebSrcQueryWithParent(GstPad*, GstObject*, GstQuery*);
 
 static void webKitWebSrcNeedData(WebKitWebSrc*);
 static void webKitWebSrcEnoughData(WebKitWebSrc*);
-static void webKitWebSrcSeek(WebKitWebSrc*);
+static gboolean webKitWebSrcSeek(WebKitWebSrc*, guint64);
 
 static GstAppSrcCallbacks appsrcCallbacks = {
     // need_data
     [](GstAppSrc*, guint, gpointer userData) {
-        WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
-        WebKitWebSrcPrivate* priv = src->priv;
-
-        {
-            WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
-            if (!priv->paused)
-                return;
-        }
-
-        GRefPtr<WebKitWebSrc> protector(src);
-        priv->notifier.notify(MainThreadSourceNotification::NeedData, [protector] { webKitWebSrcNeedData(protector.get()); });
+        webKitWebSrcNeedData(WEBKIT_WEB_SRC(userData));
     },
     // enough_data
     [](GstAppSrc*, gpointer userData) {
-        WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
-        WebKitWebSrcPrivate* priv = src->priv;
-
-        {
-            WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
-            if (priv->paused)
-                return;
-        }
-
-        GRefPtr<WebKitWebSrc> protector(src);
-        priv->notifier.notify(MainThreadSourceNotification::EnoughData, [protector] { webKitWebSrcEnoughData(protector.get()); });
+        webKitWebSrcEnoughData(WEBKIT_WEB_SRC(userData));
     },
     // seek_data
     [](GstAppSrc*, guint64 offset, gpointer userData) -> gboolean {
-        WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
-        WebKitWebSrcPrivate* priv = src->priv;
-
-        {
-            WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
-            if (offset == priv->offset && priv->requestedOffset == priv->offset)
-                return TRUE;
-
-            if (!priv->seekable)
-                return FALSE;
-
-            priv->isSeeking = true;
-            priv->requestedOffset = offset;
-        }
-
-        GRefPtr<WebKitWebSrc> protector(src);
-        priv->notifier.notify(MainThreadSourceNotification::Seek, [protector] { webKitWebSrcSeek(protector.get()); });
-        return TRUE;
+        return webKitWebSrcSeek(WEBKIT_WEB_SRC(userData), offset);
     },
     { nullptr }
 };
@@ -289,6 +259,8 @@ static void webkit_web_src_init(WebKitWebSrc* src)
     src->priv = priv;
     new (priv) WebKitWebSrcPrivate();
 
+    priv->createdInMainThread = isMainThread();
+
     priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", 0));
     if (!priv->appsrc) {
         GST_ERROR_OBJECT(src, "Failed to create appsrc");
@@ -414,28 +386,36 @@ static void webKitWebSrcStop(WebKitWebSrc* src)
 {
     WebKitWebSrcPrivate* priv = src->priv;
 
-    ASSERT(isMainThread());
+    if (priv->resource || (priv->loader && !priv->keepAlive)) {
+        GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
+        priv->notifier.cancelPendingNotifications(MainThreadSourceNotification::NeedData | MainThreadSourceNotification::EnoughData | MainThreadSourceNotification::Seek);
+        priv->notifier.notify(MainThreadSourceNotification::Stop, [protector, keepAlive = priv->keepAlive] {
+            WebKitWebSrcPrivate* priv = protector->priv;
+
+            WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(protector.get()));
+            if (priv->resource) {
+                priv->resource->stop();
+                priv->resource->setClient(nullptr);
+                priv->resource = nullptr;
+            }
+
+            if (!keepAlive)
+                priv->loader = nullptr;
+        });
+    }
 
     WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
 
     bool wasSeeking = std::exchange(priv->isSeeking, false);
 
-    priv->notifier.cancelPendingNotifications(MainThreadSourceNotification::NeedData | MainThreadSourceNotification::EnoughData | MainThreadSourceNotification::Seek);
-
-    if (priv->client) {
-        delete priv->client;
-        priv->client = 0;
-    }
-
-    if (!priv->keepAlive)
-        priv->loader = nullptr;
+    priv->client = nullptr;
 
     if (priv->buffer) {
         unmapGstBuffer(priv->buffer.get());
         priv->buffer.clear();
     }
 
-    priv->paused = FALSE;
+    priv->paused = false;
 
     priv->offset = 0;
     priv->seekable = FALSE;
@@ -512,8 +492,6 @@ static void webKitWebSrcStart(WebKitWebSrc* src)
 {
     WebKitWebSrcPrivate* priv = src->priv;
 
-    ASSERT(isMainThread());
-
     WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
 
     priv->didPassAccessControlCheck = false;
@@ -578,37 +556,41 @@ static void webKitWebSrcStart(WebKitWebSrc* src)
     // We always request Icecast/Shoutcast metadata, just in case ...
     request.setHTTPHeaderField(HTTPHeaderName::IcyMetadata, "1");
 
-    bool loadFailed = true;
-    if (priv->player && !priv->loader)
-        priv->loader = priv->player->createResourceLoader();
+    if (!priv->player || !priv->createdInMainThread) {
+        priv->client = std::make_unique<ResourceHandleStreamingClient>(src, WTFMove(request));
+        if (priv->client->loadFailed()) {
+            GST_ERROR_OBJECT(src, "Failed to setup streaming client");
+            priv->client = nullptr;
+            locker.unlock();
+            webKitWebSrcStop(src);
+        } else
+            GST_DEBUG_OBJECT(src, "Started request");
+        return;
+    }
+
+    locker.unlock();
+    GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
+    priv->notifier.notify(MainThreadSourceNotification::Start, [protector, request = WTFMove(request)] {
+        WebKitWebSrcPrivate* priv = protector->priv;
+
+        WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(protector.get()));
+        if (!priv->loader)
+            priv->loader = priv->player->createResourceLoader();
 
-    if (priv->loader) {
         PlatformMediaResourceLoader::LoadOptions loadOptions = 0;
         if (request.url().protocolIsBlob())
             loadOptions |= PlatformMediaResourceLoader::LoadOption::BufferData;
         priv->resource = priv->loader->requestResource(request, loadOptions);
-        loadFailed = !priv->resource;
-
-        if (priv->resource)
-            priv->resource->setClient(std::make_unique<CachedResourceStreamingClient>(src));
-    } else {
-        priv->client = new ResourceHandleStreamingClient(src, request);
-        loadFailed = priv->client->loadFailed();
-    }
-
-    if (loadFailed) {
-        GST_ERROR_OBJECT(src, "Failed to setup streaming client");
-        if (priv->client) {
-            delete priv->client;
-            priv->client = nullptr;
+        if (priv->resource) {
+            priv->resource->setClient(std::make_unique<CachedResourceStreamingClient>(protector.get()));
+            GST_DEBUG_OBJECT(protector.get(), "Started request");
+        } else {
+            GST_ERROR_OBJECT(protector.get(), "Failed to setup streaming client");
+            priv->loader = nullptr;
+            locker.unlock();
+            webKitWebSrcStop(protector.get());
         }
-        priv->loader = nullptr;
-        priv->resource = nullptr;
-        locker.unlock();
-        webKitWebSrcStop(src);
-        return;
-    }
-    GST_DEBUG_OBJECT(src, "Started request");
+    });
 }
 
 static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition)
@@ -640,16 +622,13 @@ static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStat
     case GST_STATE_CHANGE_READY_TO_PAUSED:
     {
         GST_DEBUG_OBJECT(src, "READY->PAUSED");
-        GRefPtr<WebKitWebSrc> protector(src);
-        priv->notifier.notify(MainThreadSourceNotification::Start, [protector] { webKitWebSrcStart(protector.get()); });
+        webKitWebSrcStart(src);
         break;
     }
     case GST_STATE_CHANGE_PAUSED_TO_READY:
     {
         GST_DEBUG_OBJECT(src, "PAUSED->READY");
-        priv->notifier.cancelPendingNotifications();
-        GRefPtr<WebKitWebSrc> protector(src);
-        priv->notifier.notify(MainThreadSourceNotification::Stop, [protector] { webKitWebSrcStop(protector.get()); });
+        webKitWebSrcStop(src);
         break;
     }
     default:
@@ -781,59 +760,91 @@ static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer)
     iface->set_uri = webKitWebSrcSetUri;
 }
 
-// appsrc callbacks
-
 static void webKitWebSrcNeedData(WebKitWebSrc* src)
 {
     WebKitWebSrcPrivate* priv = src->priv;
 
-    ASSERT(isMainThread());
-
     GST_DEBUG_OBJECT(src, "Need more data");
 
     {
         WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
-        priv->paused = FALSE;
+        if (!priv->paused)
+            return;
+        priv->paused = false;
+        if (priv->client) {
+            priv->client->setDefersLoading(false);
+            return;
+        }
     }
 
-    if (priv->client)
-        priv->client->setDefersLoading(false);
-    if (priv->resource)
-        priv->resource->setDefersLoading(false);
+    GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
+    priv->notifier.notify(MainThreadSourceNotification::NeedData, [protector] {
+        WebKitWebSrcPrivate* priv = protector->priv;
+        if (priv->resource)
+            priv->resource->setDefersLoading(false);
+    });
 }
 
 static void webKitWebSrcEnoughData(WebKitWebSrc* src)
 {
     WebKitWebSrcPrivate* priv = src->priv;
 
-    ASSERT(isMainThread());
-
     GST_DEBUG_OBJECT(src, "Have enough data");
 
     {
         WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
-        priv->paused = TRUE;
+        if (priv->paused)
+            return;
+        priv->paused = true;
+        if (priv->client) {
+            priv->client->setDefersLoading(true);
+            return;
+        }
     }
 
-    if (priv->client)
-        priv->client->setDefersLoading(true);
-    if (priv->resource)
-        priv->resource->setDefersLoading(true);
+    GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
+    priv->notifier.notify(MainThreadSourceNotification::EnoughData, [protector] {
+        WebKitWebSrcPrivate* priv = protector->priv;
+        if (priv->resource)
+            priv->resource->setDefersLoading(true);
+    });
 }
 
-static void webKitWebSrcSeek(WebKitWebSrc* src)
+static gboolean webKitWebSrcSeek(WebKitWebSrc* src, guint64 offset)
 {
-    ASSERT(isMainThread());
+    WebKitWebSrcPrivate* priv = src->priv;
+
+    {
+        WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
+        if (offset == priv->offset && priv->requestedOffset == priv->offset)
+            return TRUE;
+
+        if (!priv->seekable)
+            return FALSE;
+
+        priv->isSeeking = true;
+        priv->requestedOffset = offset;
+    }
 
     GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, src->priv->requestedOffset);
+    if (priv->client) {
+        webKitWebSrcStop(src);
+        webKitWebSrcStart(src);
+        return TRUE;
+    }
 
-    webKitWebSrcStop(src);
-    webKitWebSrcStart(src);
+    GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
+    priv->notifier.notify(MainThreadSourceNotification::Seek, [protector] {
+        webKitWebSrcStop(protector.get());
+        webKitWebSrcStart(protector.get());
+    });
+    return TRUE;
 }
 
 void webKitWebSrcSetMediaPlayer(WebKitWebSrc* src, WebCore::MediaPlayer* player)
 {
     ASSERT(player);
+    ASSERT(src->priv->createdInMainThread);
     WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src));
     src->priv->player = player;
 }
@@ -1060,17 +1071,47 @@ void CachedResourceStreamingClient::loadFinished(PlatformMediaResource&)
     handleNotifyFinished();
 }
 
-ResourceHandleStreamingClient::ResourceHandleStreamingClient(WebKitWebSrc* src, const ResourceRequest& request)
+ResourceHandleStreamingClient::ResourceHandleStreamingClient(WebKitWebSrc* src, ResourceRequest&& request)
     : StreamingClient(src)
 {
-    m_resource = ResourceHandle::create(0 /*context*/, request, this, false, false);
+    LockHolder locker(m_initializeRunLoopConditionMutex);
+    m_thread = createThread("ResourceHandleStreamingClient", [this, request = WTFMove(request)] {
+        {
+            LockHolder locker(m_initializeRunLoopConditionMutex);
+            m_runLoop = &RunLoop::current();
+            m_resource = ResourceHandle::create(nullptr /*context*/, request, this, true, false);
+            m_initializeRunLoopCondition.notifyOne();
+        }
+        if (!m_resource)
+            return;
+
+        m_runLoop->dispatch([this] { m_resource->setDefersLoading(false); });
+        m_runLoop->run();
+        {
+            LockHolder locker(m_terminateRunLoopConditionMutex);
+            m_runLoop = nullptr;
+            m_resource->clearClient();
+            m_resource->cancel();
+            m_resource = nullptr;
+            m_terminateRunLoopCondition.notifyOne();
+        }
+    });
+    m_initializeRunLoopCondition.wait(m_initializeRunLoopConditionMutex);
 }
 
 ResourceHandleStreamingClient::~ResourceHandleStreamingClient()
 {
-    if (m_resource) {
-        m_resource->cancel();
-        m_resource = nullptr;
+    if (m_thread) {
+        detachThread(m_thread);
+        m_thread = 0;
+    }
+
+    if (m_runLoop == &RunLoop::current())
+        m_runLoop->stop();
+    else {
+        LockHolder locker(m_terminateRunLoopConditionMutex);
+        m_runLoop->stop();
+        m_terminateRunLoopCondition.wait(m_terminateRunLoopConditionMutex);
     }
 }
 
@@ -1081,8 +1122,10 @@ bool ResourceHandleStreamingClient::loadFailed() const
 
 void ResourceHandleStreamingClient::setDefersLoading(bool defers)
 {
-    if (m_resource)
-        m_resource->setDefersLoading(defers);
+    m_runLoop->dispatch([this, defers] {
+        if (m_resource)
+            m_resource->setDefersLoading(defers);
+    });
 }
 
 #if USE(SOUP)