<!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>[179690] trunk/Source/WebKit2</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/179690">179690</a></dd>
<dt>Author</dt> <dd>antti@apple.com</dd>
<dt>Date</dt> <dd>2015-02-05 06:31:07 -0800 (Thu, 05 Feb 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>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&lt;Deque&lt;std::unique_ptr&gt;&gt; doesn't quite work.

(WebKit::NetworkCacheStorage::store):
(WebKit::NetworkCacheStorage::update):
(WebKit::NetworkCacheStorage::clear):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebKit2ChangeLog">trunk/Source/WebKit2/ChangeLog</a></li>
<li><a href="#trunkSourceWebKit2NetworkProcesscacheNetworkCachecpp">trunk/Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp</a></li>
<li><a href="#trunkSourceWebKit2NetworkProcesscacheNetworkCacheStorageh">trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorage.h</a></li>
<li><a href="#trunkSourceWebKit2NetworkProcesscacheNetworkCacheStorageCocoamm">trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorageCocoa.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebKit2ChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/ChangeLog (179689 => 179690)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,39 @@
</span><ins>+2015-02-04  Antti Koivisto  &lt;antti@apple.com&gt;
+
+        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&lt;Deque&lt;std::unique_ptr&gt;&gt; doesn't quite work.
+
+        (WebKit::NetworkCacheStorage::store):
+        (WebKit::NetworkCacheStorage::update):
+        (WebKit::NetworkCacheStorage::clear):
+
</ins><span class="cx"> 2015-02-05  Youenn Fablet  &lt;youenn.fablet@crf.canon.fr&gt; and Xabier Rodriguez Calvar &lt;calvaris@igalia.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [Streams API] Implement a barebone ReadableStream interface
</span></span></pre></div>
<a id="trunkSourceWebKit2NetworkProcesscacheNetworkCachecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp (179689 => 179690)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -226,31 +226,22 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     auto startTime = std::chrono::system_clock::now();
</span><del>-
</del><span class="cx">     NetworkCacheKey storageKey = makeCacheKey(originalRequest);
</span><span class="cx">     unsigned priority = originalRequest.priority();
</span><span class="cx"> 
</span><del>-    // Captured data is going to be shuffled around threads. Avoid unsafe copying.
-    struct Capture {
-        WebCore::ResourceRequest originalRequest;
-        std::function&lt;void (std::unique_ptr&lt;Entry&gt;)&gt; completionHandler;
-    };
-    // FIXME: With C++14 this could use unique_ptr and initialized lambda capture
-    auto capture = std::make_shared&lt;Capture&gt;(Capture { originalRequest, completionHandler });
-
-    m_storage-&gt;retrieve(storageKey, priority, [this, capture, startTime](std::unique_ptr&lt;NetworkCacheStorage::Entry&gt; entry) {
</del><ins>+    m_storage-&gt;retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime](std::unique_ptr&lt;NetworkCacheStorage::Entry&gt; entry) {
</ins><span class="cx">         if (!entry) {
</span><span class="cx">             LOG(NetworkCache, &quot;(NetworkProcess) not found in storage&quot;);
</span><del>-            capture-&gt;completionHandler(nullptr);
</del><ins>+            completionHandler(nullptr);
</ins><span class="cx">             return false;
</span><span class="cx">         }
</span><del>-        auto decodedEntry = decodeStorageEntry(*entry, capture-&gt;originalRequest);
</del><ins>+        auto decodedEntry = decodeStorageEntry(*entry, originalRequest);
</ins><span class="cx">         bool success = !!decodedEntry;
</span><span class="cx"> #if !LOG_DISABLED
</span><span class="cx">         auto elapsedMS = std::chrono::duration_cast&lt;std::chrono::milliseconds&gt;(std::chrono::system_clock::now() - startTime).count();
</span><span class="cx"> #endif
</span><del>-        LOG(NetworkCache, &quot;(NetworkProcess) retrieve complete success=%d priority=%u time=%lldms&quot;, success, capture-&gt;originalRequest.priority(), elapsedMS);
-        capture-&gt;completionHandler(WTF::move(decodedEntry));
</del><ins>+        LOG(NetworkCache, &quot;(NetworkProcess) retrieve complete success=%d priority=%u time=%lldms&quot;, success, originalRequest.priority(), elapsedMS);
+        completionHandler(WTF::move(decodedEntry));
</ins><span class="cx">         return success;
</span><span class="cx">     });
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebKit2NetworkProcesscacheNetworkCacheStorageh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorage.h (179689 => 179690)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> #include &lt;WebCore/ResourceResponse.h&gt;
</span><span class="cx"> #include &lt;wtf/BloomFilter.h&gt;
</span><span class="cx"> #include &lt;wtf/Deque.h&gt;
</span><ins>+#include &lt;wtf/HashSet.h&gt;
</ins><span class="cx"> #include &lt;wtf/text/WTFString.h&gt;
</span><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="lines">@@ -157,9 +158,22 @@
</span><span class="cx">         NetworkCacheKey key;
</span><span class="cx">         std::function&lt;bool (std::unique_ptr&lt;Entry&gt;)&gt; completionHandler;
</span><span class="cx">     };
</span><del>-    void dispatchRetrieveOperation(const RetrieveOperation&amp;);
</del><ins>+    void dispatchRetrieveOperation(std::unique_ptr&lt;const RetrieveOperation&gt;);
</ins><span class="cx">     void dispatchPendingRetrieveOperations();
</span><span class="cx"> 
</span><ins>+    struct StoreOperation {
+        NetworkCacheKey key;
+        Entry entry;
+        std::function&lt;void (bool success)&gt; completionHandler;
+    };
+
+    struct UpdateOperation {
+        NetworkCacheKey key;
+        Entry entry;
+        Entry existingEntry;
+        std::function&lt;void (bool success)&gt; completionHandler;
+    };
+
</ins><span class="cx">     const String m_directoryPath;
</span><span class="cx"> 
</span><span class="cx">     size_t m_maximumSize { std::numeric_limits&lt;size_t&gt;::max() };
</span><span class="lines">@@ -168,11 +182,12 @@
</span><span class="cx">     std::atomic&lt;size_t&gt; m_approximateEntryCount { 0 };
</span><span class="cx">     std::atomic&lt;bool&gt; m_shrinkInProgress { false };
</span><span class="cx"> 
</span><del>-    Vector&lt;Deque&lt;RetrieveOperation&gt;&gt; m_pendingRetrieveOperationsByPriority;
-    unsigned m_activeRetrieveOperationCount { 0 };
</del><ins>+    static const int maximumRetrievePriority = 4;
+    Deque&lt;std::unique_ptr&lt;const RetrieveOperation&gt;&gt; m_pendingRetrieveOperationsByPriority[maximumRetrievePriority + 1];
</ins><span class="cx"> 
</span><del>-    typedef std::pair&lt;NetworkCacheKey, Entry&gt; KeyEntryPair;
-    HashMap&lt;NetworkCacheKey::HashType, std::shared_ptr&lt;KeyEntryPair&gt;, AlreadyHashed&gt; m_writeCache;
</del><ins>+    HashSet&lt;std::unique_ptr&lt;const RetrieveOperation&gt;&gt; m_activeRetrieveOperations;
+    HashSet&lt;std::unique_ptr&lt;const StoreOperation&gt;&gt; m_activeStoreOperations;
+    HashSet&lt;std::unique_ptr&lt;const UpdateOperation&gt;&gt; m_activeUpdateOperations;
</ins><span class="cx"> 
</span><span class="cx"> #if PLATFORM(COCOA)
</span><span class="cx">     mutable DispatchPtr&lt;dispatch_queue_t&gt; m_ioQueue;
</span></span></pre></div>
<a id="trunkSourceWebKit2NetworkProcesscacheNetworkCacheStorageCocoamm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/NetworkProcess/cache/NetworkCacheStorageCocoa.mm (179689 => 179690)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -352,29 +352,30 @@
</span><span class="cx">     });
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void NetworkCacheStorage::dispatchRetrieveOperation(const RetrieveOperation&amp; retrieve)
</del><ins>+void NetworkCacheStorage::dispatchRetrieveOperation(std::unique_ptr&lt;const RetrieveOperation&gt; retrieveOperation)
</ins><span class="cx"> {
</span><span class="cx">     ASSERT(RunLoop::isMain());
</span><span class="cx"> 
</span><del>-    ++m_activeRetrieveOperationCount;
</del><ins>+    auto&amp; retrieve = *retrieveOperation;
+    m_activeRetrieveOperations.add(WTF::move(retrieveOperation));
</ins><span class="cx"> 
</span><span class="cx">     StringCapture cachePathCapture(m_directoryPath);
</span><del>-    dispatch_async(m_ioQueue.get(), [this, retrieve, cachePathCapture] {
</del><ins>+    dispatch_async(m_ioQueue.get(), [this, &amp;retrieve, cachePathCapture] {
</ins><span class="cx">         int fd;
</span><span class="cx">         auto channel = openFileForKey(retrieve.key, FileOpenType::Read, cachePathCapture.string(), fd);
</span><span class="cx"> 
</span><span class="cx">         bool didCallCompletionHandler = false;
</span><del>-        dispatch_io_read(channel.get(), 0, std::numeric_limits&lt;size_t&gt;::max(), dispatch_get_main_queue(), [this, fd, retrieve, didCallCompletionHandler](bool done, dispatch_data_t fileData, int error) mutable {
</del><ins>+        dispatch_io_read(channel.get(), 0, std::numeric_limits&lt;size_t&gt;::max(), dispatch_get_main_queue(), [this, fd, &amp;retrieve, didCallCompletionHandler](bool done, dispatch_data_t fileData, int error) mutable {
</ins><span class="cx">             if (done) {
</span><del>-                ASSERT(m_activeRetrieveOperationCount);
-                --m_activeRetrieveOperationCount;
-                dispatchPendingRetrieveOperations();
-            }
-            if (done) {
</del><ins>+                if (error)
+                    removeEntry(retrieve.key);
+
</ins><span class="cx">                 if (!didCallCompletionHandler)
</span><span class="cx">                     retrieve.completionHandler(nullptr);
</span><del>-                if (error)
-                    removeEntry(retrieve.key);
</del><ins>+
+                ASSERT(m_activeRetrieveOperations.contains(&amp;retrieve));
+                m_activeRetrieveOperations.remove(&amp;retrieve);
+                dispatchPendingRetrieveOperations();
</ins><span class="cx">                 return;
</span><span class="cx">             }
</span><span class="cx">             ASSERT(!didCallCompletionHandler); // We are requesting maximum sized chunk so we should never get called more than once with data.
</span><span class="lines">@@ -392,10 +393,10 @@
</span><span class="cx"> {
</span><span class="cx">     ASSERT(RunLoop::isMain());
</span><span class="cx"> 
</span><del>-    const unsigned maximumActiveRetrieveOperationCount = 5;
</del><ins>+    const int maximumActiveRetrieveOperationCount = 5;
</ins><span class="cx"> 
</span><del>-    for (int priority = m_pendingRetrieveOperationsByPriority.size() - 1; priority &gt;= 0; --priority) {
-        if (m_activeRetrieveOperationCount &gt; maximumActiveRetrieveOperationCount) {
</del><ins>+    for (int priority = maximumRetrievePriority; priority &gt;= 0; --priority) {
+        if (m_activeRetrieveOperations.size() &gt; maximumActiveRetrieveOperationCount) {
</ins><span class="cx">             LOG(NetworkCacheStorage, &quot;(NetworkProcess) limiting parallel retrieves&quot;);
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="lines">@@ -406,30 +407,38 @@
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+template &lt;class T&gt; bool retrieveActive(const T&amp; operations, const NetworkCacheKey&amp; key, std::function&lt;bool (std::unique_ptr&lt;NetworkCacheStorage::Entry&gt;)&gt;&amp; completionHandler)
+{
+    for (auto&amp; operation : operations) {
+        if (operation-&gt;key == key) {
+            LOG(NetworkCacheStorage, &quot;(NetworkProcess) found store operation in progress&quot;);
+            auto entry = operation-&gt;entry;
+            dispatch_async(dispatch_get_main_queue(), [entry, completionHandler] {
+                completionHandler(std::make_unique&lt;NetworkCacheStorage::Entry&gt;(entry));
+            });
+            return true;
+        }
+    }
+    return false;
+}
+
</ins><span class="cx"> void NetworkCacheStorage::retrieve(const NetworkCacheKey&amp; key, unsigned priority, std::function&lt;bool (std::unique_ptr&lt;Entry&gt;)&gt; completionHandler)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(RunLoop::isMain());
</span><ins>+    ASSERT(priority &lt;= maximumRetrievePriority);
</ins><span class="cx"> 
</span><span class="cx">     if (!m_contentsFilter.mayContain(key.hash())) {
</span><span class="cx">         completionHandler(nullptr);
</span><span class="cx">         return;
</span><span class="cx">     }
</span><ins>+    // See if we have the resource in memory.
+    if (retrieveActive(m_activeStoreOperations, key, completionHandler))
+        return;
+    if (retrieveActive(m_activeUpdateOperations, key, completionHandler))
+        return;
</ins><span class="cx"> 
</span><del>-    // 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-&gt;first == key) {
-            LOG(NetworkCacheStorage, &quot;(NetworkProcess) found from the write cache&quot;);
-            dispatch_async(dispatch_get_main_queue(), [keyEntryPair, completionHandler] {
-                completionHandler(std::make_unique&lt;Entry&gt;(keyEntryPair-&gt;second));
-            });
-            return;
-        }
-    }
-
-    if (m_pendingRetrieveOperationsByPriority.size() &lt; priority + 1)
-        m_pendingRetrieveOperationsByPriority.grow(priority + 1);
-    m_pendingRetrieveOperationsByPriority[priority].append(RetrieveOperation { key, completionHandler });
-
</del><ins>+    // Fetch from disk.
+    m_pendingRetrieveOperationsByPriority[priority].append(std::make_unique&lt;RetrieveOperation&gt;(RetrieveOperation { key, completionHandler }));
</ins><span class="cx">     dispatchPendingRetrieveOperations();
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -440,26 +449,29 @@
</span><span class="cx">     m_contentsFilter.add(key.hash());
</span><span class="cx">     ++m_approximateEntryCount;
</span><span class="cx"> 
</span><del>-    m_writeCache.set(key.hash(), std::make_shared&lt;KeyEntryPair&gt;(key, entry));
</del><ins>+    auto storeOperation = std::make_unique&lt;StoreOperation&gt;(StoreOperation { key, entry, completionHandler });
+    auto&amp; store = *storeOperation;
+    m_activeStoreOperations.add(WTF::move(storeOperation));
</ins><span class="cx"> 
</span><span class="cx">     StringCapture cachePathCapture(m_directoryPath);
</span><del>-    dispatch_async(m_backgroundIOQueue.get(), [this, key, entry, cachePathCapture, completionHandler] {
-        auto data = encodeEntry(key, entry);
</del><ins>+    dispatch_async(m_backgroundIOQueue.get(), [this, &amp;store, cachePathCapture] {
+        auto data = encodeEntry(store.key, store.entry);
</ins><span class="cx"> 
</span><span class="cx">         int fd;
</span><del>-        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) {
</del><ins>+        auto channel = openFileForKey(store.key, FileOpenType::Create, cachePathCapture.string(), fd);
+        dispatch_io_write(channel.get(), 0, data.get(), dispatch_get_main_queue(), [this, &amp;store](bool done, dispatch_data_t, int error) {
</ins><span class="cx">             ASSERT_UNUSED(done, done);
</span><span class="cx">             LOG(NetworkCacheStorage, &quot;(NetworkProcess) write complete error=%d&quot;, error);
</span><span class="cx">             if (error) {
</span><del>-                if (m_contentsFilter.mayContain(key.hash()))
-                    m_contentsFilter.remove(key.hash());
</del><ins>+                if (m_contentsFilter.mayContain(store.key.hash()))
+                    m_contentsFilter.remove(store.key.hash());
</ins><span class="cx">                 if (m_approximateEntryCount)
</span><span class="cx">                     --m_approximateEntryCount;
</span><span class="cx">             }
</span><del>-            m_writeCache.remove(key.hash());
</del><span class="cx"> 
</span><del>-            completionHandler(!error);
</del><ins>+            store.completionHandler(!error);
+
+            m_activeStoreOperations.remove(&amp;store);
</ins><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> 
</span><span class="lines">@@ -476,34 +488,41 @@
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    m_writeCache.set(key.hash(), std::make_shared&lt;KeyEntryPair&gt;(key, updateEntry));
</del><ins>+    auto updateOperation = std::make_unique&lt;UpdateOperation&gt;(UpdateOperation { key, updateEntry, existingEntry, completionHandler });
+    auto&amp; update = *updateOperation;
+    m_activeUpdateOperations.add(WTF::move(updateOperation));
</ins><span class="cx"> 
</span><span class="cx">     // Try to update the header of an existing entry.
</span><span class="cx">     StringCapture cachePathCapture(m_directoryPath);
</span><del>-    dispatch_async(m_backgroundIOQueue.get(), [this, key, updateEntry, existingEntry, cachePathCapture, completionHandler] {
-        auto headerData = encodeEntryHeader(key, updateEntry);
-        auto existingHeaderData = encodeEntryHeader(key, existingEntry);
</del><ins>+    dispatch_async(m_backgroundIOQueue.get(), [this, &amp;update, cachePathCapture] {
+        auto headerData = encodeEntryHeader(update.key, update.entry);
+        auto existingHeaderData = encodeEntryHeader(update.key, update.existingEntry);
</ins><span class="cx"> 
</span><span class="cx">         bool pageRoundedHeaderSizeChanged = dispatch_data_get_size(headerData.get()) != dispatch_data_get_size(existingHeaderData.get());
</span><span class="cx">         if (pageRoundedHeaderSizeChanged) {
</span><span class="cx">             LOG(NetworkCacheStorage, &quot;(NetworkProcess) page-rounded header size changed, storing full entry&quot;);
</span><del>-            dispatch_async(dispatch_get_main_queue(), [this, key, updateEntry, completionHandler] {
-                store(key, updateEntry, completionHandler);
</del><ins>+            dispatch_async(dispatch_get_main_queue(), [this, &amp;update] {
+                store(update.key, update.entry, update.completionHandler);
+
+                ASSERT(m_activeUpdateOperations.contains(&amp;update));
+                m_activeUpdateOperations.remove(&amp;update);
</ins><span class="cx">             });
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         int fd;
</span><del>-        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) {
</del><ins>+        auto channel = openFileForKey(update.key, FileOpenType::Write, cachePathCapture.string(), fd);
+        dispatch_io_write(channel.get(), 0, headerData.get(), dispatch_get_main_queue(), [this, &amp;update](bool done, dispatch_data_t, int error) {
</ins><span class="cx">             ASSERT_UNUSED(done, done);
</span><span class="cx">             LOG(NetworkCacheStorage, &quot;(NetworkProcess) update complete error=%d&quot;, error);
</span><span class="cx"> 
</span><span class="cx">             if (error)
</span><del>-                removeEntry(key);
-            m_writeCache.remove(key.hash());
</del><ins>+                removeEntry(update.key);
</ins><span class="cx"> 
</span><del>-            completionHandler(!error);
</del><ins>+            update.completionHandler(!error);
+
+            ASSERT(m_activeUpdateOperations.contains(&amp;update));
+            m_activeUpdateOperations.remove(&amp;update);
</ins><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> }
</span><span class="lines">@@ -521,7 +540,6 @@
</span><span class="cx">     ASSERT(RunLoop::isMain());
</span><span class="cx">     LOG(NetworkCacheStorage, &quot;(NetworkProcess) clearing cache&quot;);
</span><span class="cx"> 
</span><del>-    m_writeCache.clear();
</del><span class="cx">     m_contentsFilter.clear();
</span><span class="cx">     m_approximateEntryCount = 0;
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>