Title: [179690] trunk/Source/WebKit2
Revision
179690
Author
[email protected]
Date
2015-02-05 06:31:07 -0800 (Thu, 05 Feb 2015)

Log Message

Avoid copying std::functions across threads in NetworkCacheStorage
https://bugs.webkit.org/show_bug.cgi?id=141273

Reviewed by Andreas Kling.

The current approach is risky. There is possiblity that captured variables are
deleted in an unexpected thread.

* NetworkProcess/cache/NetworkCache.cpp:
(WebKit::NetworkCache::retrieve):

    The capture trick here is no longer needed.

* NetworkProcess/cache/NetworkCacheStorage.h:

    For each cache operation we create Retrive/Store/UpdateOperation object kept alive by the active operation map.
    This object captures all parameters of the operation including the lambda. When the operation completes
    the object is removed from the map in the main thread, ensuring safe destruction.

* NetworkProcess/cache/NetworkCacheStorageCocoa.mm:
(WebKit::NetworkCacheStorage::dispatchRetrieveOperation):
(WebKit::NetworkCacheStorage::dispatchPendingRetrieveOperations):
(WebKit::retrieveActive):

    Instead of maintaining a separate write cache we just look through the active write and update maps.

(WebKit::NetworkCacheStorage::retrieve):

    Use fixed sized priority array rather than a dynamic one. Vector<Deque<std::unique_ptr>> doesn't quite work.

(WebKit::NetworkCacheStorage::store):
(WebKit::NetworkCacheStorage::update):
(WebKit::NetworkCacheStorage::clear):

Modified Paths

Diff

Modified: trunk/Source/WebKit2/ChangeLog (179689 => 179690)


--- trunk/Source/WebKit2/ChangeLog	2015-02-05 13:34:41 UTC (rev 179689)
+++ trunk/Source/WebKit2/ChangeLog	2015-02-05 14:31:07 UTC (rev 179690)
@@ -1,3 +1,39 @@
+2015-02-04  Antti Koivisto  <[email protected]>
+
+        Avoid copying std::functions across threads in NetworkCacheStorage
+        https://bugs.webkit.org/show_bug.cgi?id=141273
+
+        Reviewed by Andreas Kling.
+
+        The current approach is risky. There is possiblity that captured variables are
+        deleted in an unexpected thread.
+
+        * NetworkProcess/cache/NetworkCache.cpp:
+        (WebKit::NetworkCache::retrieve):
+
+            The capture trick here is no longer needed.
+
+        * NetworkProcess/cache/NetworkCacheStorage.h:
+
+            For each cache operation we create Retrive/Store/UpdateOperation object kept alive by the active operation map.
+            This object captures all parameters of the operation including the lambda. When the operation completes
+            the object is removed from the map in the main thread, ensuring safe destruction.
+
+        * NetworkProcess/cache/NetworkCacheStorageCocoa.mm:
+        (WebKit::NetworkCacheStorage::dispatchRetrieveOperation):
+        (WebKit::NetworkCacheStorage::dispatchPendingRetrieveOperations):
+        (WebKit::retrieveActive):
+
+            Instead of maintaining a separate write cache we just look through the active write and update maps.
+
+        (WebKit::NetworkCacheStorage::retrieve):
+
+            Use fixed sized priority array rather than a dynamic one. Vector<Deque<std::unique_ptr>> doesn't quite work.
+
+        (WebKit::NetworkCacheStorage::store):
+        (WebKit::NetworkCacheStorage::update):
+        (WebKit::NetworkCacheStorage::clear):
+
 2015-02-05  Youenn Fablet  <[email protected]> and Xabier Rodriguez Calvar <[email protected]>
 
         [Streams API] Implement a barebone ReadableStream interface

Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp (179689 => 179690)


--- trunk/Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp	2015-02-05 13:34:41 UTC (rev 179689)
+++ trunk/Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp	2015-02-05 14:31:07 UTC (rev 179690)
@@ -226,31 +226,22 @@
     }
 
     auto startTime = std::chrono::system_clock::now();
-
     NetworkCacheKey storageKey = makeCacheKey(originalRequest);
     unsigned priority = originalRequest.priority();
 
-    // Captured data is going to be shuffled around threads. Avoid unsafe copying.
-    struct Capture {
-        WebCore::ResourceRequest originalRequest;
-        std::function<void (std::unique_ptr<Entry>)> completionHandler;
-    };
-    // FIXME: With C++14 this could use unique_ptr and initialized lambda capture
-    auto capture = std::make_shared<Capture>(Capture { originalRequest, completionHandler });
-
-    m_storage->retrieve(storageKey, priority, [this, capture, startTime](std::unique_ptr<NetworkCacheStorage::Entry> entry) {
+    m_storage->retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime](std::unique_ptr<NetworkCacheStorage::Entry> entry) {
         if (!entry) {
             LOG(NetworkCache, "(NetworkProcess) not found in storage");
-            capture->completionHandler(nullptr);
+            completionHandler(nullptr);
             return false;
         }
-        auto decodedEntry = decodeStorageEntry(*entry, capture->originalRequest);
+        auto decodedEntry = decodeStorageEntry(*entry, originalRequest);
         bool success = !!decodedEntry;
 #if !LOG_DISABLED
         auto elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - startTime).count();
 #endif
-        LOG(NetworkCache, "(NetworkProcess) retrieve complete success=%d priority=%u time=%lldms", success, capture->originalRequest.priority(), elapsedMS);
-        capture->completionHandler(WTF::move(decodedEntry));
+        LOG(NetworkCache, "(NetworkProcess) retrieve complete success=%d priority=%u time=%lldms", success, originalRequest.priority(), elapsedMS);
+        completionHandler(WTF::move(decodedEntry));
         return success;
     });
 }

Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorage.h (179689 => 179690)


--- trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorage.h	2015-02-05 13:34:41 UTC (rev 179689)
+++ trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorage.h	2015-02-05 14:31:07 UTC (rev 179690)
@@ -32,6 +32,7 @@
 #include <WebCore/ResourceResponse.h>
 #include <wtf/BloomFilter.h>
 #include <wtf/Deque.h>
+#include <wtf/HashSet.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
@@ -157,9 +158,22 @@
         NetworkCacheKey key;
         std::function<bool (std::unique_ptr<Entry>)> completionHandler;
     };
-    void dispatchRetrieveOperation(const RetrieveOperation&);
+    void dispatchRetrieveOperation(std::unique_ptr<const RetrieveOperation>);
     void dispatchPendingRetrieveOperations();
 
+    struct StoreOperation {
+        NetworkCacheKey key;
+        Entry entry;
+        std::function<void (bool success)> completionHandler;
+    };
+
+    struct UpdateOperation {
+        NetworkCacheKey key;
+        Entry entry;
+        Entry existingEntry;
+        std::function<void (bool success)> completionHandler;
+    };
+
     const String m_directoryPath;
 
     size_t m_maximumSize { std::numeric_limits<size_t>::max() };
@@ -168,11 +182,12 @@
     std::atomic<size_t> m_approximateEntryCount { 0 };
     std::atomic<bool> m_shrinkInProgress { false };
 
-    Vector<Deque<RetrieveOperation>> m_pendingRetrieveOperationsByPriority;
-    unsigned m_activeRetrieveOperationCount { 0 };
+    static const int maximumRetrievePriority = 4;
+    Deque<std::unique_ptr<const RetrieveOperation>> m_pendingRetrieveOperationsByPriority[maximumRetrievePriority + 1];
 
-    typedef std::pair<NetworkCacheKey, Entry> KeyEntryPair;
-    HashMap<NetworkCacheKey::HashType, std::shared_ptr<KeyEntryPair>, AlreadyHashed> m_writeCache;
+    HashSet<std::unique_ptr<const RetrieveOperation>> m_activeRetrieveOperations;
+    HashSet<std::unique_ptr<const StoreOperation>> m_activeStoreOperations;
+    HashSet<std::unique_ptr<const UpdateOperation>> m_activeUpdateOperations;
 
 #if PLATFORM(COCOA)
     mutable DispatchPtr<dispatch_queue_t> m_ioQueue;

Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorageCocoa.mm (179689 => 179690)


--- trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorageCocoa.mm	2015-02-05 13:34:41 UTC (rev 179689)
+++ trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorageCocoa.mm	2015-02-05 14:31:07 UTC (rev 179690)
@@ -352,29 +352,30 @@
     });
 }
 
-void NetworkCacheStorage::dispatchRetrieveOperation(const RetrieveOperation& retrieve)
+void NetworkCacheStorage::dispatchRetrieveOperation(std::unique_ptr<const RetrieveOperation> retrieveOperation)
 {
     ASSERT(RunLoop::isMain());
 
-    ++m_activeRetrieveOperationCount;
+    auto& retrieve = *retrieveOperation;
+    m_activeRetrieveOperations.add(WTF::move(retrieveOperation));
 
     StringCapture cachePathCapture(m_directoryPath);
-    dispatch_async(m_ioQueue.get(), [this, retrieve, cachePathCapture] {
+    dispatch_async(m_ioQueue.get(), [this, &retrieve, cachePathCapture] {
         int fd;
         auto channel = openFileForKey(retrieve.key, FileOpenType::Read, cachePathCapture.string(), fd);
 
         bool didCallCompletionHandler = false;
-        dispatch_io_read(channel.get(), 0, std::numeric_limits<size_t>::max(), dispatch_get_main_queue(), [this, fd, retrieve, didCallCompletionHandler](bool done, dispatch_data_t fileData, int error) mutable {
+        dispatch_io_read(channel.get(), 0, std::numeric_limits<size_t>::max(), dispatch_get_main_queue(), [this, fd, &retrieve, didCallCompletionHandler](bool done, dispatch_data_t fileData, int error) mutable {
             if (done) {
-                ASSERT(m_activeRetrieveOperationCount);
-                --m_activeRetrieveOperationCount;
-                dispatchPendingRetrieveOperations();
-            }
-            if (done) {
+                if (error)
+                    removeEntry(retrieve.key);
+
                 if (!didCallCompletionHandler)
                     retrieve.completionHandler(nullptr);
-                if (error)
-                    removeEntry(retrieve.key);
+
+                ASSERT(m_activeRetrieveOperations.contains(&retrieve));
+                m_activeRetrieveOperations.remove(&retrieve);
+                dispatchPendingRetrieveOperations();
                 return;
             }
             ASSERT(!didCallCompletionHandler); // We are requesting maximum sized chunk so we should never get called more than once with data.
@@ -392,10 +393,10 @@
 {
     ASSERT(RunLoop::isMain());
 
-    const unsigned maximumActiveRetrieveOperationCount = 5;
+    const int maximumActiveRetrieveOperationCount = 5;
 
-    for (int priority = m_pendingRetrieveOperationsByPriority.size() - 1; priority >= 0; --priority) {
-        if (m_activeRetrieveOperationCount > maximumActiveRetrieveOperationCount) {
+    for (int priority = maximumRetrievePriority; priority >= 0; --priority) {
+        if (m_activeRetrieveOperations.size() > maximumActiveRetrieveOperationCount) {
             LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel retrieves");
             return;
         }
@@ -406,30 +407,38 @@
     }
 }
 
+template <class T> bool retrieveActive(const T& operations, const NetworkCacheKey& key, std::function<bool (std::unique_ptr<NetworkCacheStorage::Entry>)>& completionHandler)
+{
+    for (auto& operation : operations) {
+        if (operation->key == key) {
+            LOG(NetworkCacheStorage, "(NetworkProcess) found store operation in progress");
+            auto entry = operation->entry;
+            dispatch_async(dispatch_get_main_queue(), [entry, completionHandler] {
+                completionHandler(std::make_unique<NetworkCacheStorage::Entry>(entry));
+            });
+            return true;
+        }
+    }
+    return false;
+}
+
 void NetworkCacheStorage::retrieve(const NetworkCacheKey& key, unsigned priority, std::function<bool (std::unique_ptr<Entry>)> completionHandler)
 {
     ASSERT(RunLoop::isMain());
+    ASSERT(priority <= maximumRetrievePriority);
 
     if (!m_contentsFilter.mayContain(key.hash())) {
         completionHandler(nullptr);
         return;
     }
+    // See if we have the resource in memory.
+    if (retrieveActive(m_activeStoreOperations, key, completionHandler))
+        return;
+    if (retrieveActive(m_activeUpdateOperations, key, completionHandler))
+        return;
 
-    // Write cache is a temporary memory cache used to respond to requests while a write is pending.
-    if (auto keyEntryPair = m_writeCache.get(key.hash())) {
-        if (keyEntryPair->first == key) {
-            LOG(NetworkCacheStorage, "(NetworkProcess) found from the write cache");
-            dispatch_async(dispatch_get_main_queue(), [keyEntryPair, completionHandler] {
-                completionHandler(std::make_unique<Entry>(keyEntryPair->second));
-            });
-            return;
-        }
-    }
-
-    if (m_pendingRetrieveOperationsByPriority.size() < priority + 1)
-        m_pendingRetrieveOperationsByPriority.grow(priority + 1);
-    m_pendingRetrieveOperationsByPriority[priority].append(RetrieveOperation { key, completionHandler });
-
+    // Fetch from disk.
+    m_pendingRetrieveOperationsByPriority[priority].append(std::make_unique<RetrieveOperation>(RetrieveOperation { key, completionHandler }));
     dispatchPendingRetrieveOperations();
 }
 
@@ -440,26 +449,29 @@
     m_contentsFilter.add(key.hash());
     ++m_approximateEntryCount;
 
-    m_writeCache.set(key.hash(), std::make_shared<KeyEntryPair>(key, entry));
+    auto storeOperation = std::make_unique<StoreOperation>(StoreOperation { key, entry, completionHandler });
+    auto& store = *storeOperation;
+    m_activeStoreOperations.add(WTF::move(storeOperation));
 
     StringCapture cachePathCapture(m_directoryPath);
-    dispatch_async(m_backgroundIOQueue.get(), [this, key, entry, cachePathCapture, completionHandler] {
-        auto data = "" entry);
+    dispatch_async(m_backgroundIOQueue.get(), [this, &store, cachePathCapture] {
+        auto data = "" store.entry);
 
         int fd;
-        auto channel = openFileForKey(key, FileOpenType::Create, cachePathCapture.string(), fd);
-        dispatch_io_write(channel.get(), 0, data.get(), dispatch_get_main_queue(), [this, key, completionHandler](bool done, dispatch_data_t, int error) {
+        auto channel = openFileForKey(store.key, FileOpenType::Create, cachePathCapture.string(), fd);
+        dispatch_io_write(channel.get(), 0, data.get(), dispatch_get_main_queue(), [this, &store](bool done, dispatch_data_t, int error) {
             ASSERT_UNUSED(done, done);
             LOG(NetworkCacheStorage, "(NetworkProcess) write complete error=%d", error);
             if (error) {
-                if (m_contentsFilter.mayContain(key.hash()))
-                    m_contentsFilter.remove(key.hash());
+                if (m_contentsFilter.mayContain(store.key.hash()))
+                    m_contentsFilter.remove(store.key.hash());
                 if (m_approximateEntryCount)
                     --m_approximateEntryCount;
             }
-            m_writeCache.remove(key.hash());
 
-            completionHandler(!error);
+            store.completionHandler(!error);
+
+            m_activeStoreOperations.remove(&store);
         });
     });
 
@@ -476,34 +488,41 @@
         return;
     }
 
-    m_writeCache.set(key.hash(), std::make_shared<KeyEntryPair>(key, updateEntry));
+    auto updateOperation = std::make_unique<UpdateOperation>(UpdateOperation { key, updateEntry, existingEntry, completionHandler });
+    auto& update = *updateOperation;
+    m_activeUpdateOperations.add(WTF::move(updateOperation));
 
     // Try to update the header of an existing entry.
     StringCapture cachePathCapture(m_directoryPath);
-    dispatch_async(m_backgroundIOQueue.get(), [this, key, updateEntry, existingEntry, cachePathCapture, completionHandler] {
-        auto headerData = encodeEntryHeader(key, updateEntry);
-        auto existingHeaderData = encodeEntryHeader(key, existingEntry);
+    dispatch_async(m_backgroundIOQueue.get(), [this, &update, cachePathCapture] {
+        auto headerData = encodeEntryHeader(update.key, update.entry);
+        auto existingHeaderData = encodeEntryHeader(update.key, update.existingEntry);
 
         bool pageRoundedHeaderSizeChanged = dispatch_data_get_size(headerData.get()) != dispatch_data_get_size(existingHeaderData.get());
         if (pageRoundedHeaderSizeChanged) {
             LOG(NetworkCacheStorage, "(NetworkProcess) page-rounded header size changed, storing full entry");
-            dispatch_async(dispatch_get_main_queue(), [this, key, updateEntry, completionHandler] {
-                store(key, updateEntry, completionHandler);
+            dispatch_async(dispatch_get_main_queue(), [this, &update] {
+                store(update.key, update.entry, update.completionHandler);
+
+                ASSERT(m_activeUpdateOperations.contains(&update));
+                m_activeUpdateOperations.remove(&update);
             });
             return;
         }
 
         int fd;
-        auto channel = openFileForKey(key, FileOpenType::Write, cachePathCapture.string(), fd);
-        dispatch_io_write(channel.get(), 0, headerData.get(), dispatch_get_main_queue(), [this, key, completionHandler](bool done, dispatch_data_t, int error) {
+        auto channel = openFileForKey(update.key, FileOpenType::Write, cachePathCapture.string(), fd);
+        dispatch_io_write(channel.get(), 0, headerData.get(), dispatch_get_main_queue(), [this, &update](bool done, dispatch_data_t, int error) {
             ASSERT_UNUSED(done, done);
             LOG(NetworkCacheStorage, "(NetworkProcess) update complete error=%d", error);
 
             if (error)
-                removeEntry(key);
-            m_writeCache.remove(key.hash());
+                removeEntry(update.key);
 
-            completionHandler(!error);
+            update.completionHandler(!error);
+
+            ASSERT(m_activeUpdateOperations.contains(&update));
+            m_activeUpdateOperations.remove(&update);
         });
     });
 }
@@ -521,7 +540,6 @@
     ASSERT(RunLoop::isMain());
     LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
 
-    m_writeCache.clear();
     m_contentsFilter.clear();
     m_approximateEntryCount = 0;
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to