Bug 913807 - HTTP cache v2: file I/O, off by default, r=honzab
authorMichal Novotny <michal.novotny@gmail.com>
Fri, 20 Sep 2013 11:11:26 +0200
changeset 148126 4d66cd80b6be17120ae9aabc93dbcb6ed9c161db
parent 148125 0c91d9aa9476a9c2c6eb01e6142267d7987977e2
child 148127 289fcfb608d6c1ce6dc86df359179870362e43af
push id25327
push userkwierso@gmail.com
push dateSat, 21 Sep 2013 02:29:24 +0000
treeherdermozilla-central@a2c31dc69ab3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs913807
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 913807 - HTTP cache v2: file I/O, off by default, r=honzab
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileChunk.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileInputStream.cpp
netwerk/cache2/CacheFileInputStream.h
netwerk/cache2/CacheFileMetadata.cpp
netwerk/cache2/CacheFileMetadata.h
netwerk/cache2/CacheFileOutputStream.cpp
netwerk/cache2/CacheFileOutputStream.h
netwerk/cache2/CacheHashUtils.cpp
netwerk/cache2/CacheHashUtils.h
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,1634 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+
+#include "CacheFileChunk.h"
+#include "CacheFileInputStream.h"
+#include "CacheFileOutputStream.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+#include "nsComponentManagerUtils.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+#define kMetadataWriteDelay        5000
+
+class NotifyCacheFileListenerEvent : public nsRunnable {
+public:
+  NotifyCacheFileListenerEvent(CacheFileListener *aCallback,
+                               nsresult aResult,
+                               bool aIsNew)
+    : mCallback(aCallback)
+    , mRV(aResult)
+    , mIsNew(aIsNew)
+  {
+    LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
+         "[this=%p]", this));
+    MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent);
+  }
+
+  ~NotifyCacheFileListenerEvent()
+  {
+    LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
+         "[this=%p]", this));
+    MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnFileReady(mRV, mIsNew);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileListener> mCallback;
+  nsresult                    mRV;
+  bool                        mIsNew;
+};
+
+class NotifyChunkListenerEvent : public nsRunnable {
+public:
+  NotifyChunkListenerEvent(CacheFileChunkListener *aCallback,
+                           nsresult aResult,
+                           uint32_t aChunkIdx,
+                           CacheFileChunk *aChunk)
+    : mCallback(aCallback)
+    , mRV(aResult)
+    , mChunkIdx(aChunkIdx)
+    , mChunk(aChunk)
+  {
+    LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_CTOR(NotifyChunkListenerEvent);
+  }
+
+  ~NotifyChunkListenerEvent()
+  {
+    LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_DTOR(NotifyChunkListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+  nsresult                         mRV;
+  uint32_t                         mChunkIdx;
+  nsRefPtr<CacheFileChunk>         mChunk;
+};
+
+class MetadataWriteTimer : public nsITimerCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+
+  MetadataWriteTimer(CacheFile *aFile);
+  nsresult Fire();
+  nsresult Cancel();
+  bool     ShouldFireNew();
+
+protected:
+  virtual ~MetadataWriteTimer();
+
+  nsCOMPtr<nsIWeakReference> mFile;
+  nsCOMPtr<nsITimer>         mTimer;
+  nsCOMPtr<nsIEventTarget>   mTarget;
+  PRIntervalTime             mFireTime;
+};
+
+NS_IMPL_ADDREF(MetadataWriteTimer)
+NS_IMPL_RELEASE(MetadataWriteTimer)
+
+NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile)
+  : mFireTime(0)
+{
+  LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]",
+       this, aFile));
+  MOZ_COUNT_CTOR(MetadataWriteTimer);
+
+  mFile = do_GetWeakReference(static_cast<CacheFileChunkListener *>(aFile));
+  mTarget = NS_GetCurrentThread();
+}
+
+MetadataWriteTimer::~MetadataWriteTimer()
+{
+  LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this));
+  MOZ_COUNT_DTOR(MetadataWriteTimer);
+
+  NS_ProxyRelease(mTarget, mTimer.forget().get());
+  NS_ProxyRelease(mTarget, mFile.forget().get());
+}
+
+NS_IMETHODIMP
+MetadataWriteTimer::Notify(nsITimer *aTimer)
+{
+  LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer));
+
+  MOZ_ASSERT(aTimer == mTimer);
+
+  nsCOMPtr<nsISupports> supp = do_QueryReferent(mFile);
+  if (!supp)
+    return NS_OK;
+
+  CacheFile *file = static_cast<CacheFile *>(
+                      static_cast<CacheFileChunkListener *>(supp.get()));
+
+  CacheFileAutoLock lock(file);
+
+  if (file->mTimer != this)
+    return NS_OK;
+
+  if (file->mMemoryOnly)
+    return NS_OK;
+
+  file->WriteMetadataIfNeeded();
+  file->mTimer = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+MetadataWriteTimer::Fire()
+{
+  LOG(("MetadataWriteTimer::Fire() [this=%p]", this));
+
+  nsresult rv;
+
+#ifdef DEBUG
+  bool onCurrentThread = false;
+  rv = mTarget->IsOnCurrentThread(&onCurrentThread);
+  MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread);
+#endif
+
+  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mTimer->InitWithCallback(this, kMetadataWriteDelay,
+                                nsITimer::TYPE_ONE_SHOT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mFireTime = PR_IntervalNow();
+
+  return NS_OK;
+}
+
+nsresult
+MetadataWriteTimer::Cancel()
+{
+  LOG(("MetadataWriteTimer::Cancel() [this=%p]", this));
+  return mTimer->Cancel();
+}
+
+bool
+MetadataWriteTimer::ShouldFireNew()
+{
+  uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime);
+
+  if (delta > kMetadataWriteDelay / 2) {
+    LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]",
+         this));
+    return true;
+  }
+
+  LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]",
+       this));
+  return false;
+}
+
+class DoomFileHelper : public CacheFileIOListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  DoomFileHelper(CacheFileListener *aListener)
+    : mListener(aListener)
+  {
+    MOZ_COUNT_CTOR(DoomFileHelper);
+  }
+
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    mListener->OnFileDoomed(aResult);
+    return NS_OK;
+  }
+
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+  {
+    MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+private:
+  virtual ~DoomFileHelper()
+  {
+    MOZ_COUNT_DTOR(DoomFileHelper);
+  }
+
+  nsCOMPtr<CacheFileListener>  mListener;
+};
+
+NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener)
+
+
+// We try to write metadata when the last reference to CacheFile is released.
+// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and
+// if writing fails synchronously the listener is released and lock in
+// CacheFile::Release() is re-entered. This helper class ensures that the
+// listener is released outside the lock in case of synchronous failure.
+class MetadataListenerHelper : public CacheFileMetadataListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  MetadataListenerHelper(CacheFile *aFile)
+    : mFile(aFile)
+  {
+    MOZ_COUNT_CTOR(MetadataListenerHelper);
+    mListener = static_cast<CacheFileMetadataListener *>(aFile);
+  }
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult)
+  {
+    MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  NS_IMETHOD OnMetadataWritten(nsresult aResult)
+  {
+    mFile = nullptr;
+    return mListener->OnMetadataWritten(aResult);
+  }
+
+private:
+  virtual ~MetadataListenerHelper()
+  {
+    MOZ_COUNT_DTOR(MetadataListenerHelper);
+    if (mFile) {
+      mFile->ReleaseOutsideLock(mListener.forget().get());
+    }
+  }
+
+  CacheFile*                           mFile;
+  nsCOMPtr<CacheFileMetadataListener>  mListener;
+};
+
+NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener)
+
+
+NS_IMPL_ADDREF(CacheFile)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFile::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFile");
+
+  MOZ_ASSERT(count != 0, "Unexpected");
+
+  if (count == 1) {
+    bool deleteFile = false;
+
+    // Not using CacheFileAutoLock since it hard-refers the file
+    // and in it's destructor reenters this method.
+    Lock();
+
+    if (mMemoryOnly) {
+      deleteFile = true;
+    }
+    else if (mMetadata) {
+      WriteMetadataIfNeeded();
+      if (mWritingMetadata) {
+        MOZ_ASSERT(mRefCnt > 1);
+      } else {
+        if (mRefCnt == 1)
+          deleteFile = true;
+      }
+    }
+
+    Unlock();
+
+    if (!deleteFile) {
+      return count;
+    }
+
+    NS_LOG_RELEASE(this, 0, "CacheFile");
+    delete (this);
+    return 0;
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFile)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
+                                   mozilla::net::CacheFileChunkListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFile::CacheFile()
+  : mLock("CacheFile.mLock")
+  , mOpeningFile(false)
+  , mReady(false)
+  , mMemoryOnly(false)
+  , mDataAccessed(false)
+  , mDataIsDirty(false)
+  , mWritingMetadata(false)
+  , mStatus(NS_OK)
+  , mDataSize(-1)
+  , mOutput(nullptr)
+{
+  LOG(("CacheFile::CacheFile() [this=%p]", this));
+
+  NS_ADDREF(this);
+  MOZ_COUNT_CTOR(CacheFile);
+}
+
+CacheFile::~CacheFile()
+{
+  LOG(("CacheFile::~CacheFile() [this=%p]", this));
+
+  MOZ_COUNT_DTOR(CacheFile);
+}
+
+nsresult
+CacheFile::Init(const nsACString &aKey,
+                bool aCreateNew,
+                bool aMemoryOnly,
+                bool aPriority,
+                bool aKeyIsHash,
+                CacheFileListener *aCallback)
+{
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mHandle);
+
+  nsresult rv;
+
+  mKey = aKey;
+  mMemoryOnly = aMemoryOnly;
+  mKeyIsHash = aKeyIsHash;
+
+  LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
+       "listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly, aCallback));
+
+  if (mMemoryOnly) {
+    MOZ_ASSERT(!aCallback);
+
+    MOZ_ASSERT(!mKeyIsHash);
+    mMetadata = new CacheFileMetadata(mKey);
+    mReady = true;
+    mDataSize = mMetadata->Offset();
+    return NS_OK;
+  }
+  else {
+    uint32_t flags;
+    if (aCreateNew) {
+      MOZ_ASSERT(!aCallback);
+      flags = CacheFileIOManager::CREATE_NEW;
+
+      // make sure we can use this entry immediately
+      MOZ_ASSERT(!mKeyIsHash);
+      mMetadata = new CacheFileMetadata(mKey);
+      mReady = true;
+      mDataSize = mMetadata->Offset();
+    }
+    else
+      flags = CacheFileIOManager::CREATE;
+
+    if (aPriority)
+      flags |= CacheFileIOManager::PRIORITY;
+    if (aKeyIsHash)
+      flags |= CacheFileIOManager::NOHASH;
+
+    mOpeningFile = true;
+    mListener = aCallback;
+    rv = CacheFileIOManager::OpenFile(mKey, flags, this);
+    if (NS_FAILED(rv)) {
+      mListener = nullptr;
+      mOpeningFile = false;
+
+      if (aCreateNew) {
+        NS_WARNING("Forcing memory-only entry since OpenFile failed");
+        LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+             "synchronously. We can continue in memory-only mode since "
+             "aCreateNew == true. [this=%p]", this));
+
+        mMemoryOnly = true;
+      }
+      else if (rv == NS_ERROR_NOT_INITIALIZED) {
+        NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
+                   "initialized.");
+        LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
+             "initializing entry as memory-only. [this=%p]", this));
+
+        mMemoryOnly = true;
+        MOZ_ASSERT(!mKeyIsHash);
+        mMetadata = new CacheFileMetadata(mKey);
+        mReady = true;
+        mDataSize = mMetadata->Offset();
+
+        nsRefPtr<NotifyCacheFileListenerEvent> ev;
+        ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
+        rv = NS_DispatchToCurrentThread(ev);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      else {
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(this);
+
+  nsresult rv;
+
+  uint32_t index = aChunk->Index();
+
+  LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
+       this, aResult, aChunk, index));
+
+  // TODO handle ERROR state
+
+  if (HaveChunkListeners(index)) {
+    rv = NotifyChunkListeners(index, aResult, aChunk);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(this);
+
+  nsresult rv;
+
+  LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
+       this, aResult, aChunk, aChunk->Index()));
+
+  MOZ_ASSERT(!mMemoryOnly);
+
+  // TODO handle ERROR state
+
+  if (NS_FAILED(aResult)) {
+    // TODO ??? doom entry
+    // TODO mark this chunk as memory only, since it wasn't written to disk and
+    // therefore cannot be released from memory
+    // LOG
+  }
+
+  if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
+    // update hash value in metadata
+    mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
+  }
+
+  // notify listeners if there is any
+  if (HaveChunkListeners(aChunk->Index())) {
+    // don't release the chunk since there are some listeners queued
+    rv = NotifyChunkListeners(aChunk->Index(), NS_OK, aChunk);
+    if (NS_SUCCEEDED(rv)) {
+      MOZ_ASSERT(aChunk->mRefCnt != 2);
+      return NS_OK;
+    }
+  }
+
+  if (aChunk->mRefCnt != 2) {
+    LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
+         " refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
+
+    return NS_OK;
+  }
+
+  LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]",
+       this, aChunk));
+
+  aChunk->mRemovingChunk = true;
+  ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                       aChunk->mFile.forget().get()));
+  mCachedChunks.Put(aChunk->Index(), aChunk);
+  mChunks.Remove(aChunk->Index());
+  WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                            CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  nsresult rv;
+
+  nsCOMPtr<CacheFileListener> listener;
+  bool isNew = false;
+  nsresult retval = NS_OK;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    MOZ_ASSERT(mOpeningFile);
+    MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) ||
+               (NS_FAILED(aResult) && !aHandle));
+    MOZ_ASSERT((mListener && !mMetadata) || // !createNew
+               (!mListener && mMetadata));  // createNew
+    MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry
+
+    LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08x, handle=%p]",
+         this, aResult, aHandle));
+
+    mOpeningFile = false;
+
+    if (mMemoryOnly) {
+      // We can be here only in case the entry was initilized as createNew and
+      // SetMemoryOnly() was called.
+
+      // Just don't store the handle into mHandle and exit
+      return NS_OK;
+    }
+    else if (NS_FAILED(aResult)) {
+      if (mMetadata) {
+        // This entry was initialized as createNew, just switch to memory-only
+        // mode.
+        NS_WARNING("Forcing memory-only entry since OpenFile failed");
+        LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
+             "failed asynchronously. We can continue in memory-only mode since "
+             "aCreateNew == true. [this=%p]", this));
+
+        mMemoryOnly = true;
+        return NS_OK;
+      }
+      else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+        // CacheFileIOManager doesn't have mCacheDirectory, switch to
+        // memory-only mode.
+        NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
+                   "have mCacheDirectory.");
+        LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
+             "mCacheDirectory, initializing entry as memory-only. [this=%p]",
+             this));
+
+        mMemoryOnly = true;
+        MOZ_ASSERT(!mKeyIsHash);
+        mMetadata = new CacheFileMetadata(mKey);
+        mReady = true;
+        mDataSize = mMetadata->Offset();
+
+        isNew = true;
+        retval = NS_OK;
+      }
+      else {
+        // CacheFileIOManager::OpenFile() failed for another reason.
+        isNew = false;
+        retval = aResult;
+      }
+
+      mListener.swap(listener);
+    }
+    else {
+      mHandle = aHandle;
+
+      if (mMetadata) {
+        // The entry was initialized as createNew, don't try to read metadata.
+        mMetadata->SetHandle(mHandle);
+
+        // Write all cached chunks, otherwise thay may stay unwritten.
+        mCachedChunks.Enumerate(&CacheFile::WriteAllCachedChunks, this);
+
+        return NS_OK;
+      }
+    }
+  }
+
+  if (listener) {
+    listener->OnFileReady(retval, isNew);
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(NS_SUCCEEDED(aResult));
+  MOZ_ASSERT(!mMetadata);
+  MOZ_ASSERT(mListener);
+
+  mMetadata = new CacheFileMetadata(mHandle, mKey, mKeyIsHash);
+
+  rv = mMetadata->ReadMetadata(this);
+  if (NS_FAILED(rv)) {
+    mListener.swap(listener);
+    listener->OnFileReady(rv, false);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                         nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnDataRead should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OnMetadataRead(nsresult aResult)
+{
+  MOZ_ASSERT(mListener);
+
+  LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
+
+  bool isNew = false;
+  if (NS_SUCCEEDED(aResult)) {
+    mReady = true;
+    mDataSize = mMetadata->Offset();
+    if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
+      isNew = true;
+      mMetadata->MarkDirty();
+    }
+  }
+
+  nsCOMPtr<CacheFileListener> listener;
+  mListener.swap(listener);
+  listener->OnFileReady(aResult, isNew);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnMetadataWritten(nsresult aResult)
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08x]", this, aResult));
+
+  MOZ_ASSERT(mWritingMetadata);
+  mWritingMetadata = false;
+
+  MOZ_ASSERT(!mMemoryOnly);
+  MOZ_ASSERT(!mOpeningFile);
+
+  if (NS_FAILED(aResult)) {
+    // TODO close streams with an error ???
+  }
+
+  if (mOutput || mInputs.Length() || mChunks.Count())
+    return NS_OK;
+
+  if (IsDirty())
+    WriteMetadataIfNeeded();
+
+  if (!mWritingMetadata) {
+    LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
+         this));
+    CacheFileIOManager::ReleaseNSPRHandle(mHandle);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  nsCOMPtr<CacheFileListener> listener;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    MOZ_ASSERT(mListener);
+
+    LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08x, handle=%p]",
+         this, aResult, aHandle));
+
+    mListener.swap(listener);
+  }
+
+  listener->OnFileDoomed(aResult);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFile::OpenInputStream(nsIInputStream **_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  if (!mReady) {
+    LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  CacheFileInputStream *input = new CacheFileInputStream(this);
+
+  LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
+       input, this));
+
+  mInputs.AppendElement(input);
+  NS_ADDREF(input);
+
+  mDataAccessed = true;
+  NS_ADDREF(*_retval = input);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::OpenOutputStream(nsIOutputStream **_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  if (!mReady) {
+    LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (mOutput) {
+    LOG(("CacheFile::OpenOutputStream() - We already have output stream %p "
+         "[this=%p]", mOutput, this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mOutput = new CacheFileOutputStream(this);
+
+  LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p "
+       "[this=%p]", mOutput, this));
+
+  mDataAccessed = true;
+  NS_ADDREF(*_retval = mOutput);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetMemoryOnly()
+{
+  LOG(("CacheFile::SetMemoryOnly() aMemoryOnly=%d [this=%p]",
+       mMemoryOnly, this));
+
+  if (mMemoryOnly)
+    return NS_OK;
+
+  MOZ_ASSERT(mReady);
+
+  if (!mReady) {
+    LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
+         this));
+
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (mDataAccessed) {
+    LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]", this));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // TODO what to do when this isn't a new entry and has an existing metadata???
+  mMemoryOnly = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFile::Doom(CacheFileListener *aCallback)
+{
+  CacheFileAutoLock lock(this);
+
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+  LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
+
+  nsresult rv;
+
+  if (mMemoryOnly) {
+    // TODO what exactly to do here?
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<CacheFileIOListener> listener;
+  if (aCallback)
+    listener = new DoomFileHelper(aCallback);
+
+  if (mHandle) {
+    rv = CacheFileIOManager::DoomFile(mHandle, listener);
+  } else {
+    rv = CacheFileIOManager::DoomFileByKey(mKey, listener);
+  }
+
+  return rv;
+}
+
+nsresult
+CacheFile::ThrowMemoryCachedData()
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
+
+  if (mOpeningFile) {
+    // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
+    // entries from being purged.
+
+    LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+         "entry is still opening the file [this=%p]", this));
+
+    return NS_ERROR_ABORT;
+  }
+
+  mCachedChunks.Clear();
+  return NS_OK;
+}
+
+nsresult
+CacheFile::GetElement(const char *aKey, const char **_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  *_retval = mMetadata->GetElement(aKey);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetElement(const char *aKey, const char *aValue)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetElement(aKey, aValue);
+}
+
+nsresult
+CacheFile::ElementsSize(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+
+  if (!mMetadata)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  *_retval = mMetadata->ElementsSize();
+  return NS_OK;
+}
+
+nsresult
+CacheFile::SetExpirationTime(uint32_t aExpirationTime)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetExpirationTime(aExpirationTime);
+}
+
+nsresult
+CacheFile::GetExpirationTime(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetExpirationTime(_retval);
+}
+
+nsresult
+CacheFile::SetLastModified(uint32_t aLastModified)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+  return mMetadata->SetLastModified(aLastModified);
+}
+
+nsresult
+CacheFile::GetLastModified(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetLastModified(_retval);
+}
+
+nsresult
+CacheFile::GetLastFetched(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetLastFetched(_retval);
+}
+
+nsresult
+CacheFile::GetFetchCount(uint32_t *_retval)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->GetFetchCount(_retval);
+}
+
+void
+CacheFile::Lock()
+{
+  mLock.Lock();
+}
+
+void
+CacheFile::Unlock()
+{
+  nsTArray<nsISupports*> objs;
+  objs.SwapElements(mObjsToRelease);
+
+  mLock.Unlock();
+
+  for (uint32_t i = 0; i < objs.Length(); i++)
+    objs[i]->Release();
+}
+
+void
+CacheFile::AssertOwnsLock()
+{
+  mLock.AssertCurrentThreadOwns();
+}
+
+void
+CacheFile::ReleaseOutsideLock(nsISupports *aObject)
+{
+  AssertOwnsLock();
+
+  mObjsToRelease.AppendElement(aObject);
+}
+
+nsresult
+CacheFile::GetChunk(uint32_t aIndex, bool aWriter,
+                    CacheFileChunkListener *aCallback, CacheFileChunk **_retval)
+{
+  CacheFileAutoLock lock(this);
+  return GetChunkLocked(aIndex, aWriter, aCallback, _retval);
+}
+
+nsresult
+CacheFile::GetChunkLocked(uint32_t aIndex, bool aWriter,
+                          CacheFileChunkListener *aCallback,
+                          CacheFileChunk **_retval)
+{
+  AssertOwnsLock();
+
+  LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%d, writer=%d, listener=%p]",
+       this, aIndex, aWriter, aCallback));
+
+  MOZ_ASSERT(mReady);
+  MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+  MOZ_ASSERT((aWriter && !aCallback) || (!aWriter && aCallback));
+
+  nsresult rv;
+
+  nsRefPtr<CacheFileChunk> chunk;
+  if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
+    LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
+         chunk.get(), this));
+
+    if (chunk->IsReady() || aWriter) {
+      chunk.swap(*_retval);
+    }
+    else {
+      rv = QueueChunkListener(aIndex, aCallback);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+
+  if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) {
+    LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
+         chunk.get(), this));
+
+    mChunks.Put(aIndex, chunk);
+    mCachedChunks.Remove(aIndex);
+    chunk->mFile = this;
+    chunk->mRemovingChunk = false;
+
+    MOZ_ASSERT(chunk->IsReady());
+
+    chunk.swap(*_retval);
+    return NS_OK;
+  }
+
+  int64_t off = aIndex * kChunkSize;
+
+  if (off < mDataSize) {
+    // We cannot be here if this is memory only entry since the chunk must exist
+    MOZ_ASSERT(!mMemoryOnly);
+
+    chunk = new CacheFileChunk(this, aIndex);
+    mChunks.Put(aIndex, chunk);
+
+    LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
+         "the disk [this=%p]", chunk.get(), this));
+
+    // Read the chunk from the disk
+    rv = chunk->Read(mHandle, std::min(static_cast<uint32_t>(mDataSize - off),
+                     static_cast<uint32_t>(kChunkSize)),
+                     mMetadata->GetHash(aIndex), this);
+    if (NS_FAILED(rv)) {
+      chunk->mRemovingChunk = true;
+      ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                           chunk->mFile.forget().get()));
+      mChunks.Remove(aIndex);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    if (aWriter) {
+      chunk.swap(*_retval);
+    }
+    else {
+      rv = QueueChunkListener(aIndex, aCallback);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+  else if (off == mDataSize) {
+    if (aWriter) {
+      // this listener is going to write to the chunk
+      chunk = new CacheFileChunk(this, aIndex);
+      mChunks.Put(aIndex, chunk);
+
+      LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
+           chunk.get(), this));
+
+      chunk->InitNew(this);
+      mMetadata->SetHash(aIndex, chunk->Hash());
+
+      if (HaveChunkListeners(aIndex)) {
+        rv = NotifyChunkListeners(aIndex, NS_OK, chunk);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      chunk.swap(*_retval);
+      return NS_OK;
+    }
+  }
+  else {
+    if (aWriter) {
+      // this chunk was requested by writer, but we need to fill the gap first
+
+      // Fill with zero the last chunk if it is incomplete
+      if (mDataSize % kChunkSize) {
+        rv = PadChunkWithZeroes(mDataSize / kChunkSize);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        MOZ_ASSERT(!(mDataSize % kChunkSize));
+      }
+
+      uint32_t startChunk = mDataSize / kChunkSize;
+
+      if (mMemoryOnly) {
+        // We need to create all missing CacheFileChunks if this is memory-only
+        // entry
+        for (uint32_t i = startChunk ; i < aIndex ; i++) {
+          rv = PadChunkWithZeroes(i);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+      else {
+        // We don't need to create CacheFileChunk for other empty chunks unless
+        // there is some input stream waiting for this chunk.
+
+        if (startChunk != aIndex) {
+          // Make sure the file contains zeroes at the end of the file
+          rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle,
+                                                      startChunk * kChunkSize,
+                                                      aIndex * kChunkSize,
+                                                      nullptr);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        for (uint32_t i = startChunk ; i < aIndex ; i++) {
+          if (HaveChunkListeners(i)) {
+            rv = PadChunkWithZeroes(i);
+            NS_ENSURE_SUCCESS(rv, rv);
+          }
+          else {
+            mMetadata->SetHash(i, kEmptyChunkHash);
+            mDataSize = (i + 1) * kChunkSize;
+          }
+        }
+      }
+
+      MOZ_ASSERT(mDataSize == off);
+      rv = GetChunkLocked(aIndex, true, nullptr, getter_AddRefs(chunk));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      chunk.swap(*_retval);
+      return NS_OK;
+    }
+  }
+
+  if (mOutput) {
+    // the chunk doesn't exist but mOutput may create it
+    rv = QueueChunkListener(aIndex, aCallback);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveChunk(CacheFileChunk *aChunk)
+{
+  nsresult rv;
+
+  // Avoid lock reentrancy by increasing the RefCnt
+  nsRefPtr<CacheFileChunk> chunk = aChunk;
+
+  {
+    CacheFileAutoLock lock(this);
+
+    LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%d]",
+         this, aChunk, aChunk->Index()));
+
+    MOZ_ASSERT(mReady);
+    MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+    if (aChunk->mRefCnt != 2) {
+      LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, "
+           "refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
+
+      // somebody got the reference before the lock was acquired
+      return NS_OK;
+    }
+
+#ifdef DEBUG
+    {
+      // We can be here iff the chunk is in the hash table
+      nsRefPtr<CacheFileChunk> chunkCheck;
+      mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
+      MOZ_ASSERT(chunkCheck == chunk);
+
+      // We also shouldn't have any queued listener for this chunk
+      ChunkListeners *listeners;
+      mChunkListeners.Get(chunk->Index(), &listeners);
+      MOZ_ASSERT(!listeners);
+    }
+#endif
+
+    if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
+      LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk "
+           "[this=%p]", this));
+
+      mDataIsDirty = true;
+
+      rv = chunk->Write(mHandle, this);
+      if (NS_FAILED(rv)) {
+        // TODO ??? doom entry
+        // TODO mark this chunk as memory only, since it wasn't written to disk
+        // and therefore cannot be released from memory
+        // LOG
+      }
+      else {
+        // Chunk will be removed in OnChunkWritten if it is still unused
+
+        // chunk needs to be released under the lock to be able to rely on
+        // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
+        chunk = nullptr;
+        return NS_OK;
+      }
+    }
+
+    LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p]",
+         this, chunk.get()));
+
+    chunk->mRemovingChunk = true;
+    ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
+                         chunk->mFile.forget().get()));
+    mCachedChunks.Put(chunk->Index(), chunk);
+    mChunks.Remove(chunk->Index());
+    if (!mMemoryOnly)
+      WriteMetadataIfNeeded();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveInput(CacheFileInputStream *aInput)
+{
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput));
+
+  DebugOnly<bool> found;
+  found = mInputs.RemoveElement(aInput);
+  MOZ_ASSERT(found);
+
+  ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput));
+
+  if (!mMemoryOnly)
+    WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::RemoveOutput(CacheFileOutputStream *aOutput)
+{
+  AssertOwnsLock();
+
+  LOG(("CacheFile::RemoveOutput() [this=%p, output=%p]", this, aOutput));
+
+  if (mOutput != aOutput) {
+    LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
+         " call [this=%p]", this));
+    return NS_OK;
+  }
+
+  mOutput = nullptr;
+
+  // Cancel all queued chunk and update listeners that cannot be satisfied
+  NotifyListenersAboutOutputRemoval();
+
+  if (!mMemoryOnly)
+    WriteMetadataIfNeeded();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::NotifyChunkListener(CacheFileChunkListener *aCallback,
+                               nsIEventTarget *aTarget,
+                               nsresult aResult,
+                               uint32_t aChunkIdx,
+                               CacheFileChunk *aChunk)
+{
+  LOG(("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
+       "rv=0x%08x, idx=%d, chunk=%p]", this, aCallback, aTarget, aResult,
+       aChunkIdx, aChunk));
+
+  nsresult rv;
+  nsRefPtr<NotifyChunkListenerEvent> ev;
+  ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
+  if (aTarget)
+    rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+  else
+    rv = NS_DispatchToCurrentThread(ev);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFile::QueueChunkListener(uint32_t aIndex,
+                              CacheFileChunkListener *aCallback)
+{
+  LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%d, listener=%p]",
+       this, aIndex, aCallback));
+
+  AssertOwnsLock();
+
+  MOZ_ASSERT(aCallback);
+
+  ChunkListenerItem *item = new ChunkListenerItem();
+  item->mTarget = NS_GetCurrentThread();
+  item->mCallback = aCallback;
+
+  ChunkListeners *listeners;
+  if (!mChunkListeners.Get(aIndex, &listeners)) {
+    listeners = new ChunkListeners();
+    mChunkListeners.Put(aIndex, listeners);
+  }
+
+  listeners->mItems.AppendElement(item);
+  return NS_OK;
+}
+
+nsresult
+CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+                                CacheFileChunk *aChunk)
+{
+  LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%d, rv=0x%08x, "
+       "chunk=%p]", this, aIndex, aResult, aChunk));
+
+  AssertOwnsLock();
+
+  nsresult rv, rv2;
+
+  ChunkListeners *listeners;
+  mChunkListeners.Get(aIndex, &listeners);
+  MOZ_ASSERT(listeners);
+
+  rv = NS_OK;
+  for (uint32_t i = 0 ; i < listeners->mItems.Length() ; i++) {
+    ChunkListenerItem *item = listeners->mItems[i];
+    rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex,
+                              aChunk);
+    if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+      rv = rv2;
+    delete item;
+  }
+
+  mChunkListeners.Remove(aIndex);
+
+  return rv;
+}
+
+bool
+CacheFile::HaveChunkListeners(uint32_t aIndex)
+{
+  ChunkListeners *listeners;
+  mChunkListeners.Get(aIndex, &listeners);
+  return !!listeners;
+}
+
+void
+CacheFile::NotifyListenersAboutOutputRemoval()
+{
+  LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
+
+  AssertOwnsLock();
+
+  // First fail all chunk listeners that wait for non-existent chunk
+  mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk,
+                            this);
+
+  // Fail all update listeners
+  mChunks.Enumerate(&CacheFile::FailUpdateListeners, this);
+}
+
+bool
+CacheFile::DataSize(int64_t* aSize)
+{
+  CacheFileAutoLock lock(this);
+
+  if (mOutput)
+    return false;
+
+  *aSize = mDataSize;
+  return true;
+}
+
+bool
+CacheFile::IsDirty()
+{
+  return mDataIsDirty || mMetadata->IsDirty();
+}
+
+void
+CacheFile::WriteMetadataIfNeeded()
+{
+  LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
+
+  nsresult rv;
+
+  AssertOwnsLock();
+  MOZ_ASSERT(!mMemoryOnly);
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  if (NS_FAILED(mStatus))
+    return;
+
+  if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
+      mWritingMetadata || mOpeningFile)
+    return;
+
+  LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]",
+       this));
+
+  nsRefPtr<MetadataListenerHelper> mlh = new MetadataListenerHelper(this);
+
+  rv = mMetadata->WriteMetadata(mDataSize, mlh);
+  if (NS_SUCCEEDED(rv)) {
+    mWritingMetadata = true;
+    mDataIsDirty = false;
+  }
+  else {
+    LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed "
+         "[this=%p]", this));
+    // TODO: close streams with error
+    if (NS_SUCCEEDED(mStatus))
+      mStatus = rv;
+  }
+}
+
+void
+CacheFile::PostWriteTimer()
+{
+  LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
+
+  nsresult rv;
+
+  AssertOwnsLock();
+
+  if (mTimer) {
+    if (mTimer->ShouldFireNew()) {
+      LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]",
+           this));
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+    else {
+      LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this));
+      return;
+    }
+  }
+
+  mTimer = new MetadataWriteTimer(this);
+
+  rv = mTimer->Fire();
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x "
+         "[this=%p]", rv, this));
+  }
+}
+
+PLDHashOperator
+CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
+                                nsRefPtr<CacheFileChunk>& aChunk,
+                                void* aClosure)
+{
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+
+  LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%d, chunk=%p]",
+       file, aIdx, aChunk.get()));
+
+  file->mChunks.Put(aIdx, aChunk);
+  aChunk->mFile = file;
+  aChunk->mRemovingChunk = false;
+
+  MOZ_ASSERT(aChunk->IsReady());
+
+  NS_ADDREF(aChunk);
+  file->ReleaseOutsideLock(aChunk);
+
+  return PL_DHASH_REMOVE;
+}
+
+PLDHashOperator
+CacheFile::FailListenersIfNonExistentChunk(
+  const uint32_t& aIdx,
+  nsAutoPtr<ChunkListeners>& aListeners,
+  void* aClosure)
+{
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+
+  LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%d]",
+       file, aIdx));
+
+  nsRefPtr<CacheFileChunk> chunk;
+  file->mChunks.Get(aIdx, getter_AddRefs(chunk));
+  if (chunk) {
+    MOZ_ASSERT(!chunk->IsReady());
+    return PL_DHASH_NEXT;
+  }
+
+  for (uint32_t i = 0 ; i < aListeners->mItems.Length() ; i++) {
+    ChunkListenerItem *item = aListeners->mItems[i];
+    file->NotifyChunkListener(item->mCallback, item->mTarget,
+                              NS_ERROR_NOT_AVAILABLE, aIdx, nullptr);
+    delete item;
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
+PLDHashOperator
+CacheFile::FailUpdateListeners(
+  const uint32_t& aIdx,
+  nsRefPtr<CacheFileChunk>& aChunk,
+  void* aClosure)
+{
+#ifdef PR_LOGGING
+  CacheFile *file = static_cast<CacheFile*>(aClosure);
+#endif
+
+  LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%d]",
+       file, aIdx));
+
+  if (aChunk->IsReady()) {
+    aChunk->NotifyUpdateListeners();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx)
+{
+  AssertOwnsLock();
+
+  // This method is used to pad last incomplete chunk with zeroes or create
+  // a new chunk full of zeroes
+  MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx);
+
+  nsresult rv;
+  nsRefPtr<CacheFileChunk> chunk;
+  rv = GetChunkLocked(aChunkIdx, true, nullptr, getter_AddRefs(chunk));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG(("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
+       " [this=%p]", aChunkIdx, chunk->DataSize(), kChunkSize - 1, this));
+
+  chunk->EnsureBufSize(kChunkSize);
+  memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize());
+
+  chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(),
+                        false);
+
+  ReleaseOutsideLock(chunk.forget().get());
+
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFile.h
@@ -0,0 +1,217 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFile__h__
+#define CacheFile__h__
+
+#include "CacheFileChunk.h"
+#include "nsWeakReference.h"
+#include "CacheFileIOManager.h"
+#include "CacheFileMetadata.h"
+#include "nsRefPtrHashtable.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Mutex.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileInputStream;
+class CacheFileOutputStream;
+class MetadataWriteTimer;
+
+#define CACHEFILELISTENER_IID \
+{ /* 95e7f284-84ba-48f9-b1fc-3a7336b4c33c */       \
+  0x95e7f284,                                      \
+  0x84ba,                                          \
+  0x48f9,                                          \
+  {0xb1, 0xfc, 0x3a, 0x73, 0x36, 0xb4, 0xc3, 0x3c} \
+}
+
+class CacheFileListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILELISTENER_IID)
+
+  NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew) = 0;
+  NS_IMETHOD OnFileDoomed(nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID)
+
+
+class CacheFile : public CacheFileChunkListener
+                , public CacheFileIOListener
+                , public CacheFileMetadataListener
+                , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFile();
+
+  nsresult Init(const nsACString &aKey,
+                bool aCreateNew,
+                bool aMemoryOnly,
+                bool aPriority,
+                bool aKeyIsHash,
+                CacheFileListener *aCallback);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult);
+  NS_IMETHOD OnMetadataWritten(nsresult aResult);
+
+  NS_IMETHOD OpenInputStream(nsIInputStream **_retval);
+  NS_IMETHOD OpenOutputStream(nsIOutputStream **_retval);
+  NS_IMETHOD SetMemoryOnly();
+  NS_IMETHOD Doom(CacheFileListener *aCallback);
+
+  nsresult   ThrowMemoryCachedData();
+
+  // metadata forwarders
+  nsresult GetElement(const char *aKey, const char **_retval);
+  nsresult SetElement(const char *aKey, const char *aValue);
+  nsresult ElementsSize(uint32_t *_retval);
+  nsresult SetExpirationTime(uint32_t aExpirationTime);
+  nsresult GetExpirationTime(uint32_t *_retval);
+  nsresult SetLastModified(uint32_t aLastModified);
+  nsresult GetLastModified(uint32_t *_retval);
+  nsresult GetLastFetched(uint32_t *_retval);
+  nsresult GetFetchCount(uint32_t *_retval);
+
+  bool DataSize(int64_t* aSize);
+  void Key(nsACString& aKey) { aKey = mKey; }
+
+private:
+  friend class CacheFileChunk;
+  friend class CacheFileInputStream;
+  friend class CacheFileOutputStream;
+  friend class CacheFileAutoLock;
+  friend class MetadataWriteTimer;
+  friend class MetadataListenerHelper;
+
+  virtual ~CacheFile();
+
+  void     Lock();
+  void     Unlock();
+  void     AssertOwnsLock();
+  void     ReleaseOutsideLock(nsISupports *aObject);
+
+  nsresult GetChunk(uint32_t aIndex, bool aWriter,
+                    CacheFileChunkListener *aCallback,
+                    CacheFileChunk **_retval);
+  nsresult GetChunkLocked(uint32_t aIndex, bool aWriter,
+                          CacheFileChunkListener *aCallback,
+                          CacheFileChunk **_retval);
+  nsresult RemoveChunk(CacheFileChunk *aChunk);
+
+  nsresult RemoveInput(CacheFileInputStream *aInput);
+  nsresult RemoveOutput(CacheFileOutputStream *aOutput);
+  nsresult NotifyChunkListener(CacheFileChunkListener *aCallback,
+                               nsIEventTarget *aTarget,
+                               nsresult aResult,
+                               uint32_t aChunkIdx,
+                               CacheFileChunk *aChunk);
+  nsresult QueueChunkListener(uint32_t aIndex,
+                              CacheFileChunkListener *aCallback);
+  nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+                                CacheFileChunk *aChunk);
+  bool     HaveChunkListeners(uint32_t aIndex);
+  void     NotifyListenersAboutOutputRemoval();
+
+  bool IsDirty();
+  void WriteMetadataIfNeeded();
+  void PostWriteTimer();
+
+  static PLDHashOperator WriteAllCachedChunks(const uint32_t& aIdx,
+                                              nsRefPtr<CacheFileChunk>& aChunk,
+                                              void* aClosure);
+
+  static PLDHashOperator FailListenersIfNonExistentChunk(
+                           const uint32_t& aIdx,
+                           nsAutoPtr<mozilla::net::ChunkListeners>& aListeners,
+                           void* aClosure);
+
+  static PLDHashOperator FailUpdateListeners(const uint32_t& aIdx,
+                                             nsRefPtr<CacheFileChunk>& aChunk,
+                                             void* aClosure);
+
+  nsresult PadChunkWithZeroes(uint32_t aChunkIdx);
+
+  mozilla::Mutex mLock;
+  bool           mOpeningFile;
+  bool           mReady;
+  bool           mMemoryOnly;
+  bool           mDataAccessed;
+  bool           mDataIsDirty;
+  bool           mWritingMetadata;
+  bool           mKeyIsHash;
+  nsresult       mStatus;
+  int64_t        mDataSize;
+  nsCString      mKey;
+
+  nsRefPtr<CacheFileHandle>    mHandle;
+  nsRefPtr<CacheFileMetadata>  mMetadata;
+  nsCOMPtr<CacheFileListener>  mListener;
+  nsRefPtr<MetadataWriteTimer> mTimer;
+
+  nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
+  nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
+  nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
+
+  nsTArray<CacheFileInputStream*> mInputs;
+  CacheFileOutputStream          *mOutput;
+
+  nsTArray<nsISupports*>          mObjsToRelease;
+};
+
+class CacheFileAutoLock {
+public:
+  CacheFileAutoLock(CacheFile *aFile)
+    : mFile(aFile)
+    , mLocked(true)
+  {
+    mFile->Lock();
+  }
+  ~CacheFileAutoLock()
+  {
+    if (mLocked)
+      mFile->Unlock();
+  }
+  void Lock()
+  {
+    MOZ_ASSERT(!mLocked);
+    mFile->Lock();
+    mLocked = true;
+  }
+  void Unlock()
+  {
+    MOZ_ASSERT(mLocked);
+    mFile->Unlock();
+    mLocked = false;
+  }
+
+private:
+  nsRefPtr<CacheFile> mFile;
+  bool mLocked;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -0,0 +1,675 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFileChunk.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsThreadUtils.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+#define kMinBufSize        512
+
+class NotifyUpdateListenerEvent : public nsRunnable {
+public:
+  NotifyUpdateListenerEvent(CacheFileChunkListener *aCallback,
+                            CacheFileChunk *aChunk)
+    : mCallback(aCallback)
+    , mChunk(aChunk)
+  {
+    LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_CTOR(NotifyUpdateListenerEvent);
+  }
+
+  ~NotifyUpdateListenerEvent()
+  {
+    LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]",
+         this));
+    MOZ_COUNT_DTOR(NotifyUpdateListenerEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
+
+    mCallback->OnChunkUpdated(mChunk);
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+  nsRefPtr<CacheFileChunk>         mChunk;
+};
+
+
+class ValidityPair {
+public:
+  ValidityPair(uint32_t aOffset, uint32_t aLen)
+    : mOffset(aOffset), mLen(aLen)
+  {}
+
+  ValidityPair& operator=(const ValidityPair& aOther) {
+    mOffset = aOther.mOffset;
+    mLen = aOther.mLen;
+    return *this;
+  }
+
+  bool Overlaps(const ValidityPair& aOther) const {
+    if ((mOffset <= aOther.mOffset && mOffset + mLen >= aOther.mOffset) ||
+        (aOther.mOffset <= mOffset && aOther.mOffset + mLen >= mOffset))
+      return true;
+
+    return false;
+  }
+
+  bool LessThan(const ValidityPair& aOther) const {
+    if (mOffset < aOther.mOffset)
+      return true;
+
+    if (mOffset == aOther.mOffset && mLen < aOther.mLen)
+      return true;
+
+    return false;
+  }
+
+  void Merge(const ValidityPair& aOther) {
+    MOZ_ASSERT(Overlaps(aOther));
+
+    uint32_t offset = std::min(mOffset, aOther.mOffset);
+    uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen);
+
+    mOffset = offset;
+    mLen = end - offset;
+  }
+
+  uint32_t Offset() { return mOffset; }
+  uint32_t Len()    { return mLen; }
+
+private:
+  uint32_t mOffset;
+  uint32_t mLen;
+};
+
+
+NS_IMPL_ADDREF(CacheFileChunk)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileChunk::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileChunk");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (!mRemovingChunk && count == 1) {
+    mFile->RemoveChunk(this);
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileChunk)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex)
+  : mIndex(aIndex)
+  , mState(INITIAL)
+  , mIsDirty(false)
+  , mRemovingChunk(false)
+  , mDataSize(0)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mRWBuf(nullptr)
+  , mRWBufSize(0)
+  , mReadHash(0)
+  , mFile(aFile)
+{
+  LOG(("CacheFileChunk::CacheFileChunk() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileChunk);
+}
+
+CacheFileChunk::~CacheFileChunk()
+{
+  LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileChunk);
+
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+
+  if (mRWBuf) {
+    free(mRWBuf);
+    mRWBuf = nullptr;
+    mRWBufSize = 0;
+  }
+
+  DoMemoryReport(MemorySize());
+}
+
+void
+CacheFileChunk::InitNew(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::InitNew() [this=%p, listener=%p]", this, aCallback));
+
+  MOZ_ASSERT(mState == INITIAL);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mRWBuf);
+
+  mBuf = static_cast<char *>(moz_xmalloc(kMinBufSize));
+  mBufSize = kMinBufSize;
+  mDataSize = 0;
+  mState = READY;
+  mIsDirty = true;
+
+  DoMemoryReport(MemorySize());
+}
+
+nsresult
+CacheFileChunk::Read(CacheFileHandle *aHandle, uint32_t aLen,
+                     CacheHashUtils::Hash16_t aHash,
+                     CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]",
+       this, aHandle, aLen, aCallback));
+
+  MOZ_ASSERT(mState == INITIAL);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mRWBuf);
+  MOZ_ASSERT(aLen);
+
+  nsresult rv;
+
+  mRWBuf = static_cast<char *>(moz_xmalloc(aLen));
+  mRWBufSize = aLen;
+
+  DoMemoryReport(MemorySize());
+
+  rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, mRWBuf, aLen,
+                                this);
+  if (NS_FAILED(rv)) {
+    mState = READING;   // TODO: properly handle error states
+//    mState = ERROR;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mState = READING;
+  mListener = aCallback;
+  mDataSize = aLen;
+  mReadHash = aHash;
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::Write(CacheFileHandle *aHandle,
+                      CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]",
+       this, aHandle, aCallback));
+
+  MOZ_ASSERT(mState == READY);
+  MOZ_ASSERT(!mRWBuf);
+  MOZ_ASSERT(mBuf);
+  MOZ_ASSERT(mDataSize); // Don't write chunk when it is empty
+
+  nsresult rv;
+
+  mRWBuf = mBuf;
+  mRWBufSize = mBufSize;
+  mBuf = nullptr;
+  mBufSize = 0;
+
+  rv = CacheFileIOManager::Write(aHandle, mIndex * kChunkSize, mRWBuf,
+                                 mDataSize, false, this);
+  if (NS_FAILED(rv)) {
+    mState = WRITING;   // TODO: properly handle error states
+//    mState = ERROR;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mState = WRITING;
+  mListener = aCallback;
+  mIsDirty = false;
+  return NS_OK;
+}
+
+void
+CacheFileChunk::WaitForUpdate(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]",
+       this, aCallback));
+
+  MOZ_ASSERT(mFile->mOutput);
+  MOZ_ASSERT(IsReady());
+
+#ifdef DEBUG
+  for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+  }
+#endif
+
+  ChunkListenerItem *item = new ChunkListenerItem();
+  item->mTarget = NS_GetCurrentThread();
+  item->mCallback = aCallback;
+
+  mUpdateListeners.AppendElement(item);
+}
+
+nsresult
+CacheFileChunk::CancelWait(CacheFileChunkListener *aCallback)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback));
+
+  MOZ_ASSERT(IsReady());
+
+  uint32_t i;
+  for (i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    ChunkListenerItem *item = mUpdateListeners[i];
+
+    if (item->mCallback == aCallback) {
+      mUpdateListeners.RemoveElementAt(i);
+      delete item;
+      break;
+    }
+  }
+
+#ifdef DEBUG
+  for ( ; i < mUpdateListeners.Length() ; i++) {
+    MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
+  }
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::NotifyUpdateListeners()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this));
+
+  MOZ_ASSERT(IsReady());
+
+  nsresult rv, rv2;
+
+  rv = NS_OK;
+  for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
+    ChunkListenerItem *item = mUpdateListeners[i];
+
+    LOG(("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p "
+         "[this=%p]", item->mCallback.get(), this));
+
+    nsRefPtr<NotifyUpdateListenerEvent> ev;
+    ev = new NotifyUpdateListenerEvent(item->mCallback, this);
+    rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+    if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+      rv = rv2;
+    delete item;
+  }
+
+  mUpdateListeners.Clear();
+
+  return rv;
+}
+
+uint32_t
+CacheFileChunk::Index()
+{
+  return mIndex;
+}
+
+CacheHashUtils::Hash16_t
+CacheFileChunk::Hash()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mBuf);
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(IsReady());
+
+  return CacheHashUtils::Hash16(BufForReading(), mDataSize);
+}
+
+uint32_t
+CacheFileChunk::DataSize()
+{
+  mFile->AssertOwnsLock();
+  return mDataSize;
+}
+
+void
+CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen, bool aEOF)
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(!aEOF, "Implement me! What to do with opened streams?");
+  MOZ_ASSERT(aOffset <= mDataSize);
+
+  LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d, EOF=%d]",
+       this, aOffset, aLen, aEOF));
+
+  mIsDirty = true;
+
+  int64_t fileSize = kChunkSize * mIndex + aOffset + aLen;
+  bool notify = false;
+
+  if (fileSize > mFile->mDataSize)
+    mFile->mDataSize = fileSize;
+
+  if (aOffset + aLen > mDataSize) {
+    mDataSize = aOffset + aLen;
+    notify = true;
+  }
+
+  if (mState == READY || mState == WRITING) {
+    MOZ_ASSERT(mValidityMap.Length() == 0);
+
+    if (notify)
+      NotifyUpdateListeners();
+
+    return;
+  }
+
+  // We're still waiting for data from the disk. This chunk cannot be used by
+  // input stream, so there must be no update listener. We also need to keep
+  // track of where the data is written so that we can correctly merge the new
+  // data with the old one.
+
+  MOZ_ASSERT(mUpdateListeners.Length() == 0);
+  MOZ_ASSERT(mState == READING);
+
+  ValidityPair pair(aOffset, aLen);
+
+  if (mValidityMap.Length() == 0) {
+    mValidityMap.AppendElement(pair);
+    return;
+  }
+
+
+  // Find out where to place this pair into the map, it can overlap with
+  // one preceding pair and all subsequent pairs.
+  uint32_t pos = 0;
+  for (pos = mValidityMap.Length() ; pos > 0 ; pos--) {
+    if (mValidityMap[pos-1].LessThan(pair)) {
+      if (mValidityMap[pos-1].Overlaps(pair)) {
+        // Merge with the preceding pair
+        mValidityMap[pos-1].Merge(pair);
+        pos--; // Point to the updated pair
+      }
+      else {
+        if (pos == mValidityMap.Length())
+          mValidityMap.AppendElement(pair);
+        else
+          mValidityMap.InsertElementAt(pos, pair);
+      }
+
+      break;
+    }
+  }
+
+  if (!pos)
+    mValidityMap.InsertElementAt(0, pair);
+
+  // Now pos points to merged or inserted pair, check whether it overlaps with
+  // subsequent pairs.
+  while (pos + 1 < mValidityMap.Length()) {
+    if (mValidityMap[pos].Overlaps(mValidityMap[pos + 1])) {
+      mValidityMap[pos].Merge(mValidityMap[pos + 1]);
+      mValidityMap.RemoveElementAt(pos + 1);
+    }
+    else {
+      break;
+    }
+  }
+}
+
+nsresult
+CacheFileChunk::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileChunk::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                              nsresult aResult)
+{
+  LOG(("CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  nsCOMPtr<CacheFileChunkListener> listener;
+
+  {
+    CacheFileAutoLock lock(mFile);
+
+    MOZ_ASSERT(mState == WRITING);
+    MOZ_ASSERT(mListener);
+
+#if 0
+    // TODO: properly handle error states
+    if (NS_FAILED(aResult)) {
+      mState = ERROR;
+    }
+    else {
+#endif
+      mState = READY;
+      if (!mBuf) {
+        mBuf = mRWBuf;
+        mBufSize = mRWBufSize;
+      }
+      else {
+        free(mRWBuf);
+      }
+
+      mRWBuf = nullptr;
+      mRWBufSize = 0;
+
+      DoMemoryReport(MemorySize());
+#if 0
+    }
+#endif
+
+    mListener.swap(listener);
+  }
+
+  listener->OnChunkWritten(aResult, this);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                           nsresult aResult)
+{
+  LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  nsCOMPtr<CacheFileChunkListener> listener;
+
+  {
+    CacheFileAutoLock lock(mFile);
+
+    MOZ_ASSERT(mState == READING);
+    MOZ_ASSERT(mListener);
+
+    if (NS_SUCCEEDED(aResult)) {
+      CacheHashUtils::Hash16_t hash = CacheHashUtils::Hash16(mRWBuf,
+                                                             mRWBufSize);
+      if (hash != mReadHash) {
+        LOG(("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is"
+             " %hx, hash in metadata is %hx. [this=%p, idx=%d]",
+             hash, mReadHash, this, mIndex));
+        aResult = NS_ERROR_FILE_CORRUPTED;
+      }
+      else {
+        if (!mBuf) {
+          // Just swap the buffers if we don't have mBuf yet
+          MOZ_ASSERT(mDataSize == mRWBufSize);
+          mBuf = mRWBuf;
+          mBufSize = mRWBufSize;
+          mRWBuf = nullptr;
+          mRWBufSize = 0;
+        } else {
+          // Merge data with write buffer
+          if (mRWBufSize < mBufSize) {
+            mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mBufSize));
+            mRWBufSize = mBufSize;
+          }
+
+          for (uint32_t i = 0 ; i < mValidityMap.Length() ; i++) {
+            memcpy(mRWBuf + mValidityMap[i].Offset(),
+                   mBuf + mValidityMap[i].Offset(), mValidityMap[i].Len());
+          }
+
+          free(mBuf);
+          mBuf = mRWBuf;
+          mBufSize = mRWBufSize;
+          mRWBuf = nullptr;
+          mRWBufSize = 0;
+
+          DoMemoryReport(MemorySize());
+        }
+      }
+    }
+
+    if (NS_FAILED(aResult)) {
+#if 0
+      // TODO: properly handle error states
+      mState = ERROR;
+#endif
+      mState = READY;
+      mDataSize = 0;
+    }
+    else {
+      mState = READY;
+    }
+
+    mListener.swap(listener);
+  }
+
+  listener->OnChunkRead(aResult, this);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileChunk::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileChunk::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+bool
+CacheFileChunk::IsReady()
+{
+  mFile->AssertOwnsLock();
+
+  return (mState == READY || mState == WRITING);
+}
+
+bool
+CacheFileChunk::IsDirty()
+{
+  mFile->AssertOwnsLock();
+
+  return mIsDirty;
+}
+
+char *
+CacheFileChunk::BufForWriting()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mBuf); // Writer should always first call EnsureBufSize()
+
+  MOZ_ASSERT((mState == READY && !mRWBuf) ||
+             (mState == WRITING && mRWBuf) ||
+             (mState == READING && mRWBuf));
+
+  return mBuf;
+}
+
+const char *
+CacheFileChunk::BufForReading()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT((mState == READY && mBuf && !mRWBuf) ||
+             (mState == WRITING && mRWBuf));
+
+  return mBuf ? mBuf : mRWBuf;
+}
+
+void
+CacheFileChunk::EnsureBufSize(uint32_t aBufSize)
+{
+  mFile->AssertOwnsLock();
+
+  if (mBufSize >= aBufSize)
+    return;
+
+  bool copy = false;
+  if (!mBuf && mState == WRITING) {
+    // We need to duplicate the data that is being written on the background
+    // thread, so make sure that all the data fits into the new buffer.
+    copy = true;
+
+    if (mRWBufSize > aBufSize)
+      aBufSize = mRWBufSize;
+  }
+
+  // find smallest power of 2 greater than or equal to aBufSize
+  aBufSize--;
+  aBufSize |= aBufSize >> 1;
+  aBufSize |= aBufSize >> 2;
+  aBufSize |= aBufSize >> 4;
+  aBufSize |= aBufSize >> 8;
+  aBufSize |= aBufSize >> 16;
+  aBufSize++;
+
+  const uint32_t minBufSize = kMinBufSize;
+  const uint32_t maxBufSize = kChunkSize;
+  aBufSize = clamped(aBufSize, minBufSize, maxBufSize);
+
+  mBuf = static_cast<char *>(moz_xrealloc(mBuf, aBufSize));
+  mBufSize = aBufSize;
+
+  if (copy)
+    memcpy(mBuf, mRWBuf, mRWBufSize);
+
+  DoMemoryReport(MemorySize());
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileChunk__h__
+#define CacheFileChunk__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+#define kChunkSize        16384
+#define kEmptyChunkHash   0xA8CA
+
+class CacheFileChunk;
+class CacheFile;
+class ValidityPair;
+
+
+#define CACHEFILECHUNKLISTENER_IID \
+{ /* baf16149-2ab5-499c-a9c2-5904eb95c288 */       \
+  0xbaf16149,                                      \
+  0x2ab5,                                          \
+  0x499c,                                          \
+  {0xa9, 0xc2, 0x59, 0x04, 0xeb, 0x95, 0xc2, 0x88} \
+}
+
+class CacheFileChunkListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILECHUNKLISTENER_IID)
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk) = 0;
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileChunkListener,
+                              CACHEFILECHUNKLISTENER_IID)
+
+
+class ChunkListenerItem {
+public:
+  ChunkListenerItem()  { MOZ_COUNT_CTOR(ChunkListenerItem); }
+  ~ChunkListenerItem() { MOZ_COUNT_DTOR(ChunkListenerItem); }
+
+  nsCOMPtr<nsIEventTarget>         mTarget;
+  nsCOMPtr<CacheFileChunkListener> mCallback;
+};
+
+class ChunkListeners {
+public:
+  ChunkListeners()  { MOZ_COUNT_CTOR(ChunkListeners); }
+  ~ChunkListeners() { MOZ_COUNT_DTOR(ChunkListeners); }
+
+  nsTArray<ChunkListenerItem *> mItems;
+};
+
+class CacheFileChunk : public CacheFileIOListener
+                     , public CacheMemoryConsumer
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileChunk(CacheFile *aFile, uint32_t aIndex);
+
+  void     InitNew(CacheFileChunkListener *aCallback);
+  nsresult Read(CacheFileHandle *aHandle, uint32_t aLen,
+                CacheHashUtils::Hash16_t aHash,
+                CacheFileChunkListener *aCallback);
+  nsresult Write(CacheFileHandle *aHandle, CacheFileChunkListener *aCallback);
+  void     WaitForUpdate(CacheFileChunkListener *aCallback);
+  nsresult CancelWait(CacheFileChunkListener *aCallback);
+  nsresult NotifyUpdateListeners();
+
+  uint32_t                 Index();
+  CacheHashUtils::Hash16_t Hash();
+  uint32_t                 DataSize();
+  void                     UpdateDataSize(uint32_t aOffset, uint32_t aLen,
+                                          bool aEOF);
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+  bool   IsReady();
+  bool   IsDirty();
+
+  char *       BufForWriting();
+  const char * BufForReading();
+  void         EnsureBufSize(uint32_t aBufSize);
+  uint32_t     MemorySize() { return mRWBufSize + mBufSize; }
+
+private:
+  friend class CacheFileInputStream;
+  friend class CacheFileOutputStream;
+  friend class CacheFile;
+
+  virtual ~CacheFileChunk();
+
+  enum EState {
+    INITIAL = 0,
+    READING = 1,
+    WRITING = 2,
+    READY   = 3,
+    ERROR   = 4
+  };
+
+  uint32_t mIndex;
+  EState   mState;
+  bool     mIsDirty;
+  bool     mRemovingChunk;
+  uint32_t mDataSize;
+
+  char    *mBuf;
+  uint32_t mBufSize;
+
+  char                    *mRWBuf;
+  uint32_t                 mRWBufSize;
+  CacheHashUtils::Hash16_t mReadHash;
+
+  nsRefPtr<CacheFile>              mFile; // is null if chunk is cached to
+                                          // prevent reference cycles
+  nsCOMPtr<CacheFileChunkListener> mListener;
+  nsTArray<ChunkListenerItem *>    mUpdateListeners;
+  nsTArray<ValidityPair>           mValidityMap;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,1780 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFileIOManager.h"
+
+#include "CacheLog.h"
+#include "../cache/nsCacheUtils.h"
+#include "CacheHashUtils.h"
+#include "CacheStorageService.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "private/pprio.h"
+#include "mozilla/VisualEventTracer.h"
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#undef CreateFile
+#undef CREATE_NEW
+#elif defined(XP_OS2)
+#define INCL_DOSERRORS
+#include <os2.h>
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+
+namespace mozilla {
+namespace net {
+
+#define kOpenHandlesLimit   64
+
+
+NS_IMPL_ADDREF(CacheFileHandle)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileHandle::Release()
+{
+  LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get()));
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileHandle");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (!mRemovingHandle && count == 1 && !mClosed) {
+    CacheFileIOManager::gInstance->CloseHandle(this);
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash,
+                                 bool aPriority)
+  : mHash(aHash)
+  , mIsDoomed(false)
+  , mRemovingHandle(false)
+  , mPriority(aPriority)
+  , mClosed(false)
+  , mInvalid(false)
+  , mFileExists(false)
+  , mFileSize(-1)
+  , mFD(nullptr)
+{
+  LOG(("CacheFileHandle::CacheFileHandle() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileHandle);
+  PR_INIT_CLIST(this);
+}
+
+CacheFileHandle::~CacheFileHandle()
+{
+  LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileHandle);
+}
+
+
+/******************************************************************************
+ *  CacheFileHandles
+ *****************************************************************************/
+
+class CacheFileHandlesEntry : public PLDHashEntryHdr
+{
+public:
+  PRCList      *mHandles;
+  SHA1Sum::Hash mHash;
+};
+
+PLDHashTableOps CacheFileHandles::mOps =
+{
+  PL_DHashAllocTable,
+  PL_DHashFreeTable,
+  HashKey,
+  MatchEntry,
+  MoveEntry,
+  ClearEntry,
+  PL_DHashFinalizeStub
+};
+
+CacheFileHandles::CacheFileHandles()
+  : mInitialized(false)
+{
+  LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileHandles);
+}
+
+CacheFileHandles::~CacheFileHandles()
+{
+  LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileHandles);
+
+  if (mInitialized)
+    Shutdown();
+}
+
+nsresult
+CacheFileHandles::Init()
+{
+  LOG(("CacheFileHandles::Init() %p", this));
+
+  MOZ_ASSERT(!mInitialized);
+  mInitialized = PL_DHashTableInit(&mTable, &mOps, nullptr,
+                                   sizeof(CacheFileHandlesEntry), 512);
+
+  return mInitialized ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+CacheFileHandles::Shutdown()
+{
+  LOG(("CacheFileHandles::Shutdown() %p", this));
+
+  if (mInitialized) {
+    PL_DHashTableFinish(&mTable);
+    mInitialized = false;
+  }
+}
+
+PLDHashNumber
+CacheFileHandles::HashKey(PLDHashTable *table, const void *key)
+{
+  const SHA1Sum::Hash *hash = static_cast<const SHA1Sum::Hash *>(key);
+  return static_cast<PLDHashNumber>(((*hash)[0] << 24) | ((*hash)[1] << 16) |
+                                    ((*hash)[2] << 8) | (*hash)[3]);
+}
+
+bool
+CacheFileHandles::MatchEntry(PLDHashTable *table,
+                             const PLDHashEntryHdr *header,
+                             const void *key)
+{
+  const CacheFileHandlesEntry *entry;
+
+  entry = static_cast<const CacheFileHandlesEntry *>(header);
+
+  return (memcmp(&entry->mHash, key, sizeof(SHA1Sum::Hash)) == 0);
+}
+
+void
+CacheFileHandles::MoveEntry(PLDHashTable *table,
+                            const PLDHashEntryHdr *from,
+                            PLDHashEntryHdr *to)
+{
+  const CacheFileHandlesEntry *src;
+  CacheFileHandlesEntry *dst;
+
+  src = static_cast<const CacheFileHandlesEntry *>(from);
+  dst = static_cast<CacheFileHandlesEntry *>(to);
+
+  dst->mHandles = src->mHandles;
+  memcpy(&dst->mHash, &src->mHash, sizeof(SHA1Sum::Hash));
+
+  LOG(("CacheFileHandles::MoveEntry() hash=%08x%08x%08x%08x%08x "
+       "moving from %p to %p", LOGSHA1(src->mHash), from, to));
+
+  // update pointer to mHash in all handles
+  CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(dst->mHandles);
+  while (handle != dst->mHandles) {
+    handle->mHash = &dst->mHash;
+    handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+  }
+}
+
+void
+CacheFileHandles::ClearEntry(PLDHashTable *table,
+                             PLDHashEntryHdr *header)
+{
+  CacheFileHandlesEntry *entry = static_cast<CacheFileHandlesEntry *>(header);
+  delete entry->mHandles;
+  entry->mHandles = nullptr;
+}
+
+nsresult
+CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
+                            CacheFileHandle **_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  // find hash entry for key
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable,
+                         (void *)aHash,
+                         PL_DHASH_LOOKUP));
+  if (PL_DHASH_ENTRY_IS_FREE(entry)) {
+    LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+         "no handle found", LOGSHA1(aHash)));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Check if the entry is doomed
+  CacheFileHandle *handle = static_cast<CacheFileHandle *>(
+                              PR_LIST_HEAD(entry->mHandles));
+  if (handle->IsDoomed()) {
+    LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+         "found doomed handle %p, entry %p", LOGSHA1(aHash), handle, entry));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+       "found handle %p, entry %p", LOGSHA1(aHash), handle, entry));
+  NS_ADDREF(*_retval = handle);
+  return NS_OK;
+}
+
+
+nsresult
+CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
+                            bool aPriority,
+                            CacheFileHandle **_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  // find hash entry for key
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable,
+                         (void *)aHash,
+                         PL_DHASH_ADD));
+  if (!entry) return NS_ERROR_OUT_OF_MEMORY;
+
+  if (!entry->mHandles) {
+    // new entry
+    entry->mHandles = new PRCList;
+    memcpy(&entry->mHash, aHash, sizeof(SHA1Sum::Hash));
+    PR_INIT_CLIST(entry->mHandles);
+
+    LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+         "created new entry %p, list %p", LOGSHA1(aHash), entry,
+         entry->mHandles));
+  }
+#ifdef DEBUG
+  else {
+    MOZ_ASSERT(!PR_CLIST_IS_EMPTY(entry->mHandles));
+    CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+    MOZ_ASSERT(handle->IsDoomed());
+  }
+#endif
+
+  nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(&entry->mHash, aPriority);
+  PR_APPEND_LINK(handle, entry->mHandles);
+
+  LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+       "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
+
+  NS_ADDREF(*_retval = handle);
+  handle.forget();
+  return NS_OK;
+}
+
+void
+CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(mInitialized);
+
+  void *key = (void *)aHandle->Hash();
+
+  CacheFileHandlesEntry *entry;
+  entry = static_cast<CacheFileHandlesEntry *>(
+    PL_DHashTableOperate(&mTable, key, PL_DHASH_LOOKUP));
+
+  MOZ_ASSERT(PL_DHASH_ENTRY_IS_BUSY(entry));
+
+#ifdef DEBUG
+  {
+    CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+    bool handleFound = false;
+    while (handle != entry->mHandles) {
+      if (handle == aHandle) {
+        handleFound = true;
+        break;
+      }
+      handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+    }
+    MOZ_ASSERT(handleFound);
+  }
+#endif
+
+  LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+       "removing handle %p", LOGSHA1(&entry->mHash), aHandle));
+
+  PR_REMOVE_AND_INIT_LINK(aHandle);
+  NS_RELEASE(aHandle);
+
+  if (PR_CLIST_IS_EMPTY(entry->mHandles)) {
+    LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+         "list %p is empty, removing entry %p", LOGSHA1(&entry->mHash),
+         entry->mHandles, entry));
+    PL_DHashTableOperate(&mTable, key, PL_DHASH_REMOVE);
+  }
+}
+
+PLDHashOperator
+GetHandles(PLDHashTable    *table,
+           PLDHashEntryHdr *hdr,
+           uint32_t         number,
+           void *           arg)
+{
+  const CacheFileHandlesEntry *entry;
+  nsTArray<nsRefPtr<CacheFileHandle> > *handles;
+
+  entry = static_cast<const CacheFileHandlesEntry *>(hdr);
+  handles = reinterpret_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(arg);
+
+  CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles);
+  while (handle != entry->mHandles) {
+    handles->AppendElement(handle);
+    handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+void
+CacheFileHandles::GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
+{
+  MOZ_ASSERT(mInitialized);
+
+  PL_DHashTableEnumerate(&mTable, GetHandles, _retval);
+}
+
+uint32_t
+CacheFileHandles::HandleCount()
+{
+  MOZ_ASSERT(mInitialized);
+
+  return mTable.entryCount;
+}
+
+// Events
+
+class ShutdownEvent : public nsRunnable {
+public:
+  ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar)
+    : mLock(aLock)
+    , mCondVar(aCondVar)
+  {
+    MOZ_COUNT_CTOR(ShutdownEvent);
+  }
+
+  ~ShutdownEvent()
+  {
+    MOZ_COUNT_DTOR(ShutdownEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    MutexAutoLock lock(*mLock);
+
+    CacheFileIOManager::gInstance->ShutdownInternal();
+
+    mCondVar->Notify();
+    return NS_OK;
+  }
+
+protected:
+  mozilla::Mutex   *mLock;
+  mozilla::CondVar *mCondVar;
+};
+
+class OpenFileEvent : public nsRunnable {
+public:
+  OpenFileEvent(const nsACString &aKey,
+                uint32_t aFlags,
+                CacheFileIOListener *aCallback)
+    : mFlags(aFlags)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+    , mKey(aKey)
+  {
+    MOZ_COUNT_CTOR(OpenFileEvent);
+
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+    mIOMan = CacheFileIOManager::gInstance;
+    MOZ_ASSERT(mTarget);
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aKey.BeginReading());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+  }
+
+  ~OpenFileEvent()
+  {
+    MOZ_COUNT_DTOR(OpenFileEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      mRV = NS_OK;
+
+      if (mFlags & CacheFileIOManager::NOHASH) {
+        nsACString::const_char_iterator begin, end;
+        begin = mKey.BeginReading();
+        end = mKey.EndReading();
+        uint32_t i = 0;
+        while (begin != end && i < (SHA1Sum::HashSize << 1)) {
+          if (!(i & 1))
+            mHash[i >> 1] = 0;
+          uint8_t shift = (i & 1) ? 0 : 4;
+          if (*begin >= '0' && *begin <= '9')
+            mHash[i >> 1] |= (*begin - '0') << shift;
+          else if (*begin >= 'A' && *begin <= 'F')
+            mHash[i >> 1] |= (*begin - 'A' + 10) << shift;
+          else
+            break;
+
+          ++i;
+          ++begin;
+        }
+
+        if (i != (SHA1Sum::HashSize << 1) || begin != end)
+          mRV = NS_ERROR_INVALID_ARG;
+      }
+      else {
+        SHA1Sum sum;
+        sum.update(mKey.BeginReading(), mKey.Length());
+        sum.finish(mHash);
+      }
+
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+      if (NS_SUCCEEDED(mRV)) {
+        if (!mIOMan)
+          mRV = NS_ERROR_NOT_INITIALIZED;
+        else {
+          mRV = mIOMan->OpenFileInternal(&mHash, mFlags, getter_AddRefs(mHandle));
+          mIOMan = nullptr;
+          if (mHandle) {
+            MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get());
+            mHandle->Key() = mKey;
+          }
+        }
+      }
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+      mCallback->OnFileOpened(mHandle, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  SHA1Sum::Hash                 mHash;
+  uint32_t                      mFlags;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileIOManager>  mIOMan;
+  nsRefPtr<CacheFileHandle>     mHandle;
+  nsresult                      mRV;
+  nsCString                     mKey;
+};
+
+class CloseHandleEvent : public nsRunnable {
+public:
+  CloseHandleEvent(CacheFileHandle *aHandle)
+    : mHandle(aHandle)
+  {
+    MOZ_COUNT_CTOR(CloseHandleEvent);
+  }
+
+  ~CloseHandleEvent()
+  {
+    MOZ_COUNT_DTOR(CloseHandleEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (!mHandle->IsClosed())
+      CacheFileIOManager::gInstance->CloseHandleInternal(mHandle);
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle> mHandle;
+};
+
+class ReadEvent : public nsRunnable {
+public:
+  ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
+            int32_t aCount, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mOffset(aOffset)
+    , mBuf(aBuf)
+    , mCount(aCount)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(ReadEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+  }
+
+  ~ReadEvent()
+  {
+    MOZ_COUNT_DTOR(ReadEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->ReadInternal(
+          mHandle, mOffset, mBuf, mCount);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+      if (mCallback)
+        mCallback->OnDataRead(mHandle, mBuf, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mOffset;
+  char                         *mBuf;
+  int32_t                       mCount;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+class WriteEvent : public nsRunnable {
+public:
+  WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf,
+             int32_t aCount, bool aValidate, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mOffset(aOffset)
+    , mBuf(aBuf)
+    , mCount(aCount)
+    , mValidate(aValidate)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(WriteEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+  }
+
+  ~WriteEvent()
+  {
+    MOZ_COUNT_DTOR(WriteEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->WriteInternal(
+          mHandle, mOffset, mBuf, mCount, mValidate);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+      if (mCallback)
+        mCallback->OnDataWritten(mHandle, mBuf, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mOffset;
+  const char                   *mBuf;
+  int32_t                       mCount;
+  bool                          mValidate;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+class DoomFileEvent : public nsRunnable {
+public:
+  DoomFileEvent(CacheFileHandle *aHandle,
+                CacheFileIOListener *aCallback)
+    : mCallback(aCallback)
+    , mHandle(aHandle)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(DoomFileEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+
+    MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
+    MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+  }
+
+  ~DoomFileEvent()
+  {
+    MOZ_COUNT_DTOR(DoomFileEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
+
+      MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+      if (mCallback)
+        mCallback->OnFileDoomed(mHandle, mRV);
+      MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileHandle>     mHandle;
+  nsresult                      mRV;
+};
+
+class DoomFileByKeyEvent : public nsRunnable {
+public:
+  DoomFileByKeyEvent(const nsACString &aKey,
+                     CacheFileIOListener *aCallback)
+    : mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(DoomFileByKeyEvent);
+
+    SHA1Sum sum;
+    sum.update(aKey.BeginReading(), aKey.Length());
+    sum.finish(mHash);
+
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+    mIOMan = CacheFileIOManager::gInstance;
+    MOZ_ASSERT(mTarget);
+  }
+
+  ~DoomFileByKeyEvent()
+  {
+    MOZ_COUNT_DTOR(DoomFileByKeyEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      if (!mIOMan)
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else {
+        mRV = mIOMan->DoomFileByKeyInternal(&mHash);
+        mIOMan = nullptr;
+      }
+
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      if (mCallback)
+        mCallback->OnFileDoomed(nullptr, mRV);
+    }
+    return NS_OK;
+  }
+
+protected:
+  SHA1Sum::Hash                 mHash;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsRefPtr<CacheFileIOManager>  mIOMan;
+  nsresult                      mRV;
+};
+
+class ReleaseNSPRHandleEvent : public nsRunnable {
+public:
+  ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
+    : mHandle(aHandle)
+  {
+    MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
+  }
+
+  ~ReleaseNSPRHandleEvent()
+  {
+    MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mHandle->mFD && !mHandle->IsClosed())
+      CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
+
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+};
+
+class TruncateSeekSetEOFEvent : public nsRunnable {
+public:
+  TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
+                          int64_t aEOFPos, CacheFileIOListener *aCallback)
+    : mHandle(aHandle)
+    , mTruncatePos(aTruncatePos)
+    , mEOFPos(aEOFPos)
+    , mCallback(aCallback)
+    , mRV(NS_ERROR_FAILURE)
+  {
+    MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent);
+    mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
+  }
+
+  ~TruncateSeekSetEOFEvent()
+  {
+    MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mTarget) {
+      if (mHandle->IsClosed())
+        mRV = NS_ERROR_NOT_INITIALIZED;
+      else
+        mRV = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
+          mHandle, mTruncatePos, mEOFPos);
+
+      nsCOMPtr<nsIEventTarget> target;
+      mTarget.swap(target);
+      target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    }
+    else {
+      if (mCallback)
+        mCallback->OnEOFSet(mHandle, mRV);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CacheFileHandle>     mHandle;
+  int64_t                       mTruncatePos;
+  int64_t                       mEOFPos;
+  nsCOMPtr<CacheFileIOListener> mCallback;
+  nsCOMPtr<nsIEventTarget>      mTarget;
+  nsresult                      mRV;
+};
+
+
+CacheFileIOManager * CacheFileIOManager::gInstance = nullptr;
+
+NS_IMPL_ISUPPORTS0(CacheFileIOManager)
+
+CacheFileIOManager::CacheFileIOManager()
+  : mShuttingDown(false)
+  , mTreeCreated(false)
+{
+  LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileIOManager);
+  MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
+}
+
+CacheFileIOManager::~CacheFileIOManager()
+{
+  LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileIOManager);
+}
+
+nsresult
+CacheFileIOManager::Init()
+{
+  LOG(("CacheFileIOManager::Init()"));
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gInstance)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  nsRefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
+
+  nsresult rv = ioMan->InitInternal();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ioMan.swap(gInstance);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::InitInternal()
+{
+  nsresult rv;
+
+  mIOThread = new CacheIOThread();
+
+  rv = mIOThread->Init();
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mHandles.Init();
+  if (NS_FAILED(rv)) {
+    mIOThread->Shutdown();
+
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Shutdown()
+{
+  LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance));
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  {
+    mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock");
+    mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar");
+
+    MutexAutoLock autoLock(lock);
+    nsRefPtr<ShutdownEvent> ev = new ShutdownEvent(&lock, &condVar);
+    DebugOnly<nsresult> rv;
+    rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    condVar.Wait();
+  }
+
+  MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
+  MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
+
+  nsRefPtr<CacheFileIOManager> ioMan;
+  ioMan.swap(gInstance);
+
+  if (ioMan->mIOThread)
+    ioMan->mIOThread->Shutdown();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ShutdownInternal()
+{
+  mShuttingDown = true;
+
+  // close all handles and delete all associated files
+  nsTArray<nsRefPtr<CacheFileHandle> > handles;
+  mHandles.GetAllHandles(&handles);
+
+  for (uint32_t i=0 ; i<handles.Length() ; i++) {
+    CacheFileHandle *h = handles[i];
+    h->mRemovingHandle = true;
+    h->mClosed = true;
+
+    // Close file handle
+    if (h->mFD) {
+      ReleaseNSPRHandleInternal(h);
+    }
+
+    // Remove file if entry is doomed or invalid
+    if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) {
+      h->mFile->Remove(false);
+    }
+
+    // Remove the handle from hashtable
+    mHandles.RemoveHandle(h);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OnProfile()
+{
+  LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance));
+
+  MOZ_ASSERT(gInstance);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> directory;
+
+#if defined(MOZ_WIDGET_ANDROID)
+  char* cachePath = getenv("CACHE_DIRECTORY");
+  if (cachePath && *cachePath) {
+    rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
+                               true, getter_AddRefs(directory));
+  }
+#endif
+
+  if (!directory) {
+    rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+                                getter_AddRefs(directory));
+  }
+
+  if (!directory) {
+    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+                                getter_AddRefs(directory));
+  }
+
+  if (directory) {
+    rv = directory->Clone(getter_AddRefs(gInstance->mCacheDirectory));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = gInstance->mCacheDirectory->Append(NS_LITERAL_STRING("cache2"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// static
+already_AddRefed<nsIEventTarget>
+CacheFileIOManager::IOTarget()
+{
+  nsCOMPtr<nsIEventTarget> target;
+  if (gInstance && gInstance->mIOThread)
+    target = gInstance->mIOThread->Target();
+
+  return target.forget();
+}
+
+already_AddRefed<CacheIOThread>
+CacheFileIOManager::IOThread()
+{
+  nsRefPtr<CacheIOThread> thread;
+  if (gInstance)
+    thread = gInstance->mIOThread;
+
+  return thread.forget();
+}
+
+nsresult
+CacheFileIOManager::OpenFile(const nsACString &aKey,
+                             uint32_t aFlags,
+                             CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
+       PromiseFlatCString(aKey).get(), aFlags, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  bool priority = aFlags & CacheFileIOManager::PRIORITY;
+  nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, priority
+    ? CacheIOThread::OPEN_PRIORITY
+    : CacheIOThread::OPEN);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
+                                     uint32_t aFlags,
+                                     CacheFileHandle **_retval)
+{
+  nsresult rv;
+
+  if (mShuttingDown)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!mTreeCreated) {
+    rv = CreateCacheTree();
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  rv = GetFile(aHash, getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<CacheFileHandle> handle;
+  mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+  if (aFlags == CREATE_NEW) {
+    if (handle) {
+      rv = DoomFileInternal(handle);
+      NS_ENSURE_SUCCESS(rv, rv);
+      handle = nullptr;
+    }
+
+    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    rv = file->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (exists) {
+      rv = file->Remove(false);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Cannot remove old entry from the disk");
+        // TODO log
+      }
+    }
+
+    handle->mFile.swap(file);
+    handle->mFileSize = 0;
+  }
+
+  if (handle) {
+    handle.swap(*_retval);
+    return NS_OK;
+  }
+
+  bool exists;
+  rv = file->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!exists && aFlags == OPEN)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    rv = file->GetFileSize(&handle->mFileSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    handle->mFileExists = true;
+  }
+  else {
+    handle->mFileSize = 0;
+  }
+
+  handle->mFile.swap(file);
+  handle.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CloseHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileIOManager::CloseHandle() [handle=%p]", aHandle));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<CloseHandleEvent> ev = new CloseHandleEvent(aHandle);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle)
+{
+  // This method should be called only from CloseHandleEvent. If this handle is
+  // still unused then mRefCnt should be 2 (reference in hashtable and in
+  // CacheHandleEvent)
+
+  if (aHandle->mRefCnt != 2) {
+    // someone got this handle between calls to CloseHandle() and
+    // CloseHandleInternal()
+    return NS_OK;
+  }
+
+  // We're going to remove this handle, don't call CloseHandle() again
+  aHandle->mRemovingHandle = true;
+
+  // Close file handle
+  if (aHandle->mFD) {
+    ReleaseNSPRHandleInternal(aHandle);
+  }
+
+  // If the entry was doomed delete the file
+  if (aHandle->IsDoomed()) {
+    aHandle->mFile->Remove(false);
+  }
+
+  // Remove the handle from hashtable
+  mHandles.RemoveHandle(aHandle);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
+                         char *aBuf, int32_t aCount,
+                         CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, "
+       "listener=%p]", aHandle, aOffset, aCount, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<ReadEvent> ev = new ReadEvent(aHandle, aOffset, aBuf, aCount,
+                                         aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+    ? CacheIOThread::READ_PRIORITY
+    : CacheIOThread::READ);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                                 char *aBuf, int32_t aCount)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    NS_WARNING("Trying to read from non-existent file");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    NS_WARNING("Trying to read from non-existent file");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+  if (offset == -1)
+    return NS_ERROR_FAILURE;
+
+  int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
+  if (bytesRead != aCount)
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset,
+                          const char *aBuf, int32_t aCount, bool aValidate,
+                          CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
+       "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate,
+       aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
+                                           aValidate, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                                  const char *aBuf, int32_t aCount,
+                                  bool aValidate)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    rv = CreateFile(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Write invalidates the entry by default
+  aHandle->mInvalid = true;
+
+  int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+  if (offset == -1)
+    return NS_ERROR_FAILURE;
+
+  int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
+
+  if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten)
+      aHandle->mFileSize = aOffset+bytesWritten;
+
+  if (bytesWritten != aCount)
+    return NS_ERROR_FAILURE;
+
+  // Write was successful and this write validates the entry (i.e. metadata)
+  if (aValidate)
+    aHandle->mInvalid = false;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
+                             CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",
+       aHandle, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+    ? CacheIOThread::DOOM_PRIORITY
+    : CacheIOThread::DOOM);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
+{
+  nsresult rv;
+
+  if (aHandle->IsDoomed())
+    return NS_OK;
+
+  if (aHandle->mFileExists) {
+    // we need to move the current file to the doomed directory
+    if (aHandle->mFD)
+      ReleaseNSPRHandleInternal(aHandle);
+
+    // find unused filename
+    nsCOMPtr<nsIFile> file;
+    rv = GetDoomedFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIFile> parentDir;
+    rv = file->GetParent(getter_AddRefs(parentDir));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsAutoCString leafName;
+    rv = file->GetNativeLeafName(leafName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = aHandle->mFile->MoveToNative(parentDir, leafName);
+    if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
+      LOG(("  file already removed under our hands"));
+      aHandle->mFileExists = false;
+      rv = NS_OK;
+    }
+    else {
+      NS_ENSURE_SUCCESS(rv, rv);
+      aHandle->mFile.swap(file);
+    }
+  }
+
+  aHandle->mIsDoomed = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileByKey(const nsACString &aKey,
+                                  CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
+       PromiseFlatCString(aKey).get(), aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash)
+{
+  nsresult rv;
+
+  if (mShuttingDown)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!mCacheDirectory)
+    return NS_ERROR_FILE_INVALID_PATH;
+
+  // Find active handle
+  nsRefPtr<CacheFileHandle> handle;
+  mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+  if (handle) {
+    if (handle->IsDoomed())
+      return NS_OK;
+
+    return DoomFileInternal(handle);
+  }
+
+  // There is no handle for this file, delete the file if exists
+  nsCOMPtr<nsIFile> file;
+  rv = GetFile(aHash, getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = file->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!exists)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  rv = file->Remove(false);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Cannot remove old entry from the disk");
+    // TODO log
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(aHandle->mFD);
+
+  DebugOnly<bool> found;
+  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  MOZ_ASSERT(found);
+
+  PR_Close(aHandle->mFD);
+  aHandle->mFD = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle,
+                                       int64_t aTruncatePos, int64_t aEOFPos,
+                                       CacheFileIOListener *aCallback)
+{
+  LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
+       "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (aHandle->IsClosed() || !ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsRefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
+                                           aHandle, aTruncatePos, aEOFPos,
+                                           aCallback);
+  rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::EnumerateEntryFiles(EEnumerateMode aMode,
+                                        CacheEntriesEnumerator** aEnumerator)
+{
+  LOG(("CacheFileIOManager::EnumerateEntryFiles(%d)", aMode));
+
+  nsresult rv;
+  CacheFileIOManager *ioMan = gInstance;
+
+  if (!ioMan)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  if (!ioMan->mCacheDirectory)
+    return NS_ERROR_FILE_NOT_FOUND;
+
+  nsCOMPtr<nsIFile> file;
+  rv = ioMan->mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  switch (aMode) {
+  case ENTRIES:
+    rv = file->AppendNative(NS_LITERAL_CSTRING("entries"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+
+  case DOOMED:
+    rv = file->AppendNative(NS_LITERAL_CSTRING("doomed"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+
+  default:
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoPtr<CacheEntriesEnumerator> enumerator(
+    new CacheEntriesEnumerator(file));
+
+  rv = enumerator->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aEnumerator = enumerator.forget();
+  return NS_OK;
+}
+
+static nsresult
+TruncFile(PRFileDesc *aFD, uint32_t aEOF)
+{
+#if defined(XP_UNIX)
+  if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
+    NS_ERROR("ftruncate failed");
+    return NS_ERROR_FAILURE;
+  }
+#elif defined(XP_WIN)
+  int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET);
+  if (cnt == -1)
+    return NS_ERROR_FAILURE;
+  if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) {
+    NS_ERROR("SetEndOfFile failed");
+    return NS_ERROR_FAILURE;
+  }
+#elif defined(XP_OS2)
+  if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(aFD), aEOF) != NO_ERROR) {
+    NS_ERROR("DosSetFileSize failed");
+    return NS_ERROR_FAILURE;
+  }
+#else
+  MOZ_ASSERT(false, "Not implemented!");
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
+                                               int64_t aTruncatePos,
+                                               int64_t aEOFPos)
+{
+  nsresult rv;
+
+  if (!aHandle->mFileExists) {
+    rv = CreateFile(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!aHandle->mFD) {
+    rv = OpenNSPRHandle(aHandle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    NSPRHandleUsed(aHandle);
+  }
+
+  // Check again, OpenNSPRHandle could figure out the file was gone.
+  if (!aHandle->mFileExists) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // This operation always invalidates the entry
+  aHandle->mInvalid = true;
+
+  rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aTruncatePos));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aEOFPos));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CreateFile(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(!aHandle->mFD);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> file;
+  if (aHandle->IsDoomed()) {
+    rv = GetDoomedFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    rv = GetFile(aHandle->Hash(), getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
+      NS_WARNING("Found a file that should not exist!");
+    }
+  }
+
+  rv = OpenNSPRHandle(aHandle, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aHandle->mFileSize = 0;
+  return NS_OK;
+}
+
+void
+CacheFileIOManager::GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval)
+{
+  _retval.Assign("");
+  const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                           '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+  for (uint32_t i=0 ; i<sizeof(SHA1Sum::Hash) ; i++) {
+    _retval.Append(hexChars[(*aHash)[i] >> 4]);
+    _retval.Append(hexChars[(*aHash)[i] & 0xF]);
+  }
+}
+
+nsresult
+CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval)
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> file;
+  rv = mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("entries"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString leafName;
+  GetHashStr(aHash, leafName);
+
+  rv = file->AppendNative(leafName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  file.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::GetDoomedFile(nsIFile **_retval)
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> file;
+  rv = mCacheDirectory->Clone(getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("doomed"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  srand(static_cast<unsigned>(PR_Now()));
+  nsAutoCString leafName;
+  uint32_t iter=0;
+  while (true) {
+    iter++;
+    leafName.AppendInt(rand());
+    rv = file->SetNativeLeafName(leafName);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    if (NS_SUCCEEDED(file->Exists(&exists)) && !exists)
+      break;
+
+    leafName.Truncate();
+  }
+
+//  Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter);
+
+  file.swap(*_retval);
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir)
+{
+  nsresult rv;
+  bool exists;
+
+  nsCOMPtr<nsIFile> file;
+  if (!aDir) {
+    file = aFile;
+  } else {
+    nsAutoCString dir(aDir);
+    rv = aFile->Clone(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = file->AppendNative(dir);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = file->Exists(&exists);
+  if (NS_SUCCEEDED(rv) && !exists)
+    rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Cannot create directory");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CreateCacheTree()
+{
+  MOZ_ASSERT(!mTreeCreated);
+
+  if (!mCacheDirectory)
+    return NS_ERROR_FILE_INVALID_PATH;
+
+  nsresult rv;
+
+  // ensure parent directory exists
+  nsCOMPtr<nsIFile> parentDir;
+  rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = CheckAndCreateDir(parentDir, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure cache directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure entries directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, "entries");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // ensure doomed directory exists
+  rv = CheckAndCreateDir(mCacheDirectory, "doomed");
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mTreeCreated = true;
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
+{
+  MOZ_ASSERT(!aHandle->mFD);
+  MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
+  MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
+
+  nsresult rv;
+
+  if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
+    // close handle that hasn't been used for the longest time
+    rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (aCreate) {
+    rv = aHandle->mFile->OpenNSPRFileDesc(
+           PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aHandle->mFileExists = true;
+  }
+  else {
+    rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
+    if (NS_ERROR_FILE_NOT_FOUND == rv) {
+      LOG(("  file doesn't exists"));
+      aHandle->mFileExists = false;
+      aHandle->mIsDoomed = true;
+      return NS_OK;
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mHandlesByLastUsed.AppendElement(aHandle);
+  return NS_OK;
+}
+
+void
+CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
+{
+  MOZ_ASSERT(aHandle->mFD);
+
+  DebugOnly<bool> found;
+  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  MOZ_ASSERT(found);
+
+  mHandlesByLastUsed.AppendElement(aHandle);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileIOManager__h__
+#define CacheFileIOManager__h__
+
+#include "CacheIOThread.h"
+#include "CacheEntriesEnumerator.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+#include "mozilla/SHA1.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "pldhash.h"
+#include "prclist.h"
+#include "prio.h"
+
+class nsIFile;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileHandle : public nsISupports
+                      , public PRCList
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
+  bool IsDoomed() { return mIsDoomed; }
+  const SHA1Sum::Hash *Hash() { return mHash; }
+  int64_t FileSize() { return mFileSize; }
+  bool IsPriority() { return mPriority; }
+  bool FileExists() { return mFileExists; }
+  bool IsClosed() { return mClosed; }
+  nsCString & Key() { return mKey; }
+
+private:
+  friend class CacheFileIOManager;
+  friend class CacheFileHandles;
+  friend class ReleaseNSPRHandleEvent;
+
+  virtual ~CacheFileHandle();
+
+  const SHA1Sum::Hash *mHash;
+  bool                 mIsDoomed;
+  bool                 mRemovingHandle;
+  bool                 mPriority;
+  bool                 mClosed;
+  bool                 mInvalid;
+  bool                 mFileExists; // This means that the file should exists,
+                                    // but it can be still deleted by OS/user
+                                    // and then a subsequent OpenNSPRFileDesc()
+                                    // will fail.
+  nsCOMPtr<nsIFile>    mFile;
+  int64_t              mFileSize;
+  PRFileDesc          *mFD;  // if null then the file doesn't exists on the disk
+  nsCString            mKey;
+};
+
+class CacheFileHandles {
+public:
+  CacheFileHandles();
+  ~CacheFileHandles();
+
+  nsresult Init();
+  void     Shutdown();
+
+  nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
+  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
+  void     RemoveHandle(CacheFileHandle *aHandlle);
+  void     GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval);
+  uint32_t HandleCount();
+
+private:
+  static PLDHashNumber HashKey(PLDHashTable *table, const void *key);
+  static bool          MatchEntry(PLDHashTable *table,
+                                  const PLDHashEntryHdr *entry,
+                                  const void *key);
+  static void          MoveEntry(PLDHashTable *table,
+                                 const PLDHashEntryHdr *from,
+                                 PLDHashEntryHdr *to);
+  static void          ClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry);
+
+  static PLDHashTableOps mOps;
+  PLDHashTable           mTable;
+  bool                   mInitialized;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class OpenFileEvent;
+class CloseFileEvent;
+class ReadEvent;
+class WriteEvent;
+
+#define CACHEFILEIOLISTENER_IID \
+{ /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */       \
+  0xdcaf2ddc,                                      \
+  0x17cf,                                          \
+  0x4242,                                          \
+  {0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5} \
+}
+
+class CacheFileIOListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID)
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) = 0;
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult) = 0;
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                        nsresult aResult) = 0;
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) = 0;
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)
+
+
+class CacheFileIOManager : public nsISupports
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  enum {
+    OPEN       = 0U,
+    CREATE     = 1U,
+    CREATE_NEW = 2U,
+    PRIORITY   = 4U,
+    NOHASH     = 8U
+  };
+
+  CacheFileIOManager();
+
+  static nsresult Init();
+  static nsresult Shutdown();
+  static nsresult OnProfile();
+  static already_AddRefed<nsIEventTarget> IOTarget();
+  static already_AddRefed<CacheIOThread> IOThread();
+
+  static nsresult OpenFile(const nsACString &aKey,
+                           uint32_t aFlags,
+                           CacheFileIOListener *aCallback);
+  static nsresult Read(CacheFileHandle *aHandle, int64_t aOffset,
+                       char *aBuf, int32_t aCount,
+                       CacheFileIOListener *aCallback);
+  static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset,
+                        const char *aBuf, int32_t aCount, bool aValidate,
+                        CacheFileIOListener *aCallback);
+  static nsresult DoomFile(CacheFileHandle *aHandle,
+                           CacheFileIOListener *aCallback);
+  static nsresult DoomFileByKey(const nsACString &aKey,
+                                CacheFileIOListener *aCallback);
+  static nsresult ReleaseNSPRHandle(CacheFileHandle *aHandle);
+  static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle,
+                                     int64_t aTruncatePos, int64_t aEOFPos,
+                                     CacheFileIOListener *aCallback);
+
+  enum EEnumerateMode {
+    ENTRIES,
+    DOOMED
+  };
+
+  static nsresult EnumerateEntryFiles(EEnumerateMode aMode,
+                                      CacheEntriesEnumerator** aEnumerator);
+
+private:
+  friend class CacheFileHandle;
+  friend class CacheFileChunk;
+  friend class CacheFile;
+  friend class ShutdownEvent;
+  friend class OpenFileEvent;
+  friend class CloseHandleEvent;
+  friend class ReadEvent;
+  friend class WriteEvent;
+  friend class DoomFileEvent;
+  friend class DoomFileByKeyEvent;
+  friend class ReleaseNSPRHandleEvent;
+  friend class TruncateSeekSetEOFEvent;
+
+  virtual ~CacheFileIOManager();
+
+  static nsresult CloseHandle(CacheFileHandle *aHandle);
+
+  nsresult InitInternal();
+  nsresult ShutdownInternal();
+
+  nsresult OpenFileInternal(const SHA1Sum::Hash *aHash,
+                            uint32_t aFlags,
+                            CacheFileHandle **_retval);
+  nsresult CloseHandleInternal(CacheFileHandle *aHandle);
+  nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                        char *aBuf, int32_t aCount);
+  nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
+                         const char *aBuf, int32_t aCount, bool aValidate);
+  nsresult DoomFileInternal(CacheFileHandle *aHandle);
+  nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash);
+  nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle);
+  nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
+                                      int64_t aTruncatePos, int64_t aEOFPos);
+
+  nsresult CreateFile(CacheFileHandle *aHandle);
+  static void GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval);
+  nsresult GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval);
+  nsresult GetDoomedFile(nsIFile **_retval);
+  nsresult CheckAndCreateDir(nsIFile *aFile, const char *aDir);
+  nsresult CreateCacheTree();
+  nsresult OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate = false);
+  void     NSPRHandleUsed(CacheFileHandle *aHandle);
+
+
+  static CacheFileIOManager  *gInstance;
+  bool                        mShuttingDown;
+  nsRefPtr<CacheIOThread>     mIOThread;
+  nsCOMPtr<nsIFile>           mCacheDirectory;
+  bool                        mTreeCreated;
+  CacheFileHandles            mHandles;
+  nsTArray<CacheFileHandle *> mHandlesByLastUsed;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -0,0 +1,634 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFileInputStream.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(CacheFileInputStream)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileInputStream::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileInputStream");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  if (count == 1) {
+    mFile->RemoveInput(this);
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileInputStream::CacheFileInputStream(CacheFile *aFile)
+  : mFile(aFile)
+  , mPos(0)
+  , mClosed(false)
+  , mStatus(NS_OK)
+  , mWaitingForUpdate(false)
+  , mListeningForChunk(-1)
+  , mInReadSegments(false)
+  , mCallbackFlags(0)
+{
+  LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileInputStream);
+}
+
+CacheFileInputStream::~CacheFileInputStream()
+{
+  LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileInputStream);
+}
+
+// nsIInputStream
+NS_IMETHODIMP
+CacheFileInputStream::Close()
+{
+  LOG(("CacheFileInputStream::Close() [this=%p]", this));
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Available(uint64_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+    return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+  }
+
+  EnsureCorrectChunk(false);
+  *_retval = 0;
+
+  if (mChunk) {
+    int64_t canRead;
+    const char *buf;
+    CanRead(&canRead, &buf);
+
+    if (canRead > 0)
+      *_retval = canRead;
+    else if (canRead == 0 && !mFile->mOutput)
+      return NS_BASE_STREAM_CLOSED;
+  }
+
+  LOG(("CacheFileInputStream::Available() [this=%p, retval=%lld]",
+       this, *_retval));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Read(char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount));
+
+  nsresult rv;
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Read() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    if NS_FAILED(mStatus)
+      return mStatus;
+
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      LOG(("  no chunk, returning 0 read and NS_OK"));
+      *_retval = 0;
+      return NS_OK;
+    }
+    else {
+      LOG(("  waiting for chuck, returning WOULD_BLOCK"));
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+  }
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead < 0) {
+    // file was truncated ???
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    *_retval = 0;
+    rv = NS_OK;
+  }
+  else if (canRead > 0) {
+    *_retval = std::min(static_cast<uint32_t>(canRead), aCount);
+    memcpy(aBuf, buf, *_retval);
+    mPos += *_retval;
+
+    EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0));
+
+    rv = NS_OK;
+  }
+  else {
+    if (mFile->mOutput)
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    else {
+      *_retval = 0;
+      rv = NS_OK;
+    }
+  }
+
+  LOG(("CacheFileInputStream::Read() [this=%p, rv=0x%08x, retval=%d",
+       this, rv, *_retval));
+
+  return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+                                   uint32_t aCount, uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]",
+       this, aCount));
+
+  nsresult rv;
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    if NS_FAILED(mStatus)
+      return mStatus;
+
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      *_retval = 0;
+      return NS_OK;
+    }
+    else {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+  }
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead < 0) {
+    // file was truncated ???
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    *_retval = 0;
+    rv = NS_OK;
+  }
+  else if (canRead > 0) {
+    uint32_t toRead = std::min(static_cast<uint32_t>(canRead), aCount);
+
+    // We need to release the lock to avoid lock re-entering
+#ifdef DEBUG
+    int64_t oldPos = mPos;
+#endif
+    mInReadSegments = true;
+    lock.Unlock();
+    rv = aWriter(this, aClosure, buf, 0, toRead, _retval);
+    lock.Lock();
+    mInReadSegments = false;
+#ifdef DEBUG
+    MOZ_ASSERT(oldPos == mPos);
+#endif
+
+    if (NS_SUCCEEDED(rv)) {
+      MOZ_ASSERT(*_retval <= toRead,
+                 "writer should not write more than we asked it to write");
+      mPos += *_retval;
+    }
+
+    EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0));
+
+    rv = NS_OK;
+  }
+  else {
+    if (mFile->mOutput)
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    else {
+      *_retval = 0;
+      rv = NS_OK;
+    }
+  }
+
+  LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08x, retval=%d",
+       this, rv, *_retval));
+
+  return rv;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::IsNonBlocking(bool *_retval)
+{
+  *_retval = true;
+  return NS_OK;
+}
+
+// nsIAsyncInputStream
+NS_IMETHODIMP
+CacheFileInputStream::CloseWithStatus(nsresult aStatus)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]",
+       this, aStatus));
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+    return NS_OK;
+  }
+
+  mClosed = true;
+  mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+  if (mChunk)
+    ReleaseChunk();
+
+  // TODO propagate error from input stream to other streams ???
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
+                                uint32_t aFlags,
+                                uint32_t aRequestedCount,
+                                nsIEventTarget *aEventTarget)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+       "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
+       aRequestedCount, aEventTarget));
+
+  mCallback = aCallback;
+  mCallbackFlags = aFlags;
+
+  if (!mCallback) {
+    if (mWaitingForUpdate) {
+      mChunk->CancelWait(this);
+      mWaitingForUpdate = false;
+    }
+    return NS_OK;
+  }
+
+  if (mClosed) {
+    NotifyListener();
+    return NS_OK;
+  }
+
+  EnsureCorrectChunk(false);
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileInputStream::Seek(int32_t whence, int64_t offset)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%lld]",
+       this, whence, offset));
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  int64_t newPos = offset;
+  switch (whence) {
+    case NS_SEEK_SET:
+      break;
+    case NS_SEEK_CUR:
+      newPos += mPos;
+      break;
+    case NS_SEEK_END:
+      newPos += mFile->mDataSize;
+      break;
+    default:
+      NS_ERROR("invalid whence");
+      return NS_ERROR_INVALID_ARG;
+  }
+  mPos = newPos;
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileInputStream::Seek() [this=%p, pos=%lld]", this, mPos));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::Tell(int64_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  if (mClosed) {
+    LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  *_retval = mPos;
+
+  LOG(("CacheFileInputStream::Tell() [this=%p, retval=%lld]", this, *_retval));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileInputStream::SetEOF()
+{
+  MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// CacheFileChunkListener
+nsresult
+CacheFileInputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileInputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileInputStream::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                                       CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08x, "
+       "idx=%d, chunk=%p]", this, aResult, aChunkIdx, aChunk));
+
+  MOZ_ASSERT(mListeningForChunk != -1);
+
+  if (mListeningForChunk != static_cast<int64_t>(aChunkIdx)) {
+    // This is not a chunk that we're waiting for
+    LOG(("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
+         "different chunk. [this=%p, listeningForChunk=%lld]",
+         this, mListeningForChunk));
+
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!mChunk);
+  MOZ_ASSERT(!mWaitingForUpdate);
+  mListeningForChunk = -1;
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+
+    LOG(("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
+         "ignoring notification. [this=%p]", this));
+
+    return NS_OK;
+  }
+
+  mChunk = aChunk;
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileInputStream::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  CacheFileAutoLock lock(mFile);
+  MOZ_ASSERT(!mInReadSegments);
+
+  LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]",
+       this, aChunk->Index()));
+
+  if (!mWaitingForUpdate) {
+    LOG(("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
+         "mWaitingforUpdate == false. [this=%p]", this));
+
+    return NS_OK;
+  }
+  else {
+    mWaitingForUpdate = false;
+  }
+
+  MOZ_ASSERT(mChunk == aChunk);
+
+  MaybeNotifyListener();
+
+  return NS_OK;
+}
+
+void
+CacheFileInputStream::ReleaseChunk()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]",
+       this, mChunk->Index()));
+
+  if (mWaitingForUpdate) {
+    LOG(("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
+         "[this=%p]", this));
+
+    mChunk->CancelWait(this);
+    mWaitingForUpdate = false;
+  }
+
+  mFile->ReleaseOutsideLock(mChunk.forget().get());
+}
+
+void
+CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+       this, aReleaseOnly));
+
+  nsresult rv;
+
+  uint32_t chunkIdx = mPos / kChunkSize;
+
+  if (mChunk) {
+    if (mChunk->Index() == chunkIdx) {
+      // we have a correct chunk
+      LOG(("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
+           "[this=%p, idx=%d]", this, chunkIdx));
+
+      return;
+    }
+    else {
+      ReleaseChunk();
+    }
+  }
+
+  MOZ_ASSERT(!mWaitingForUpdate);
+
+  if (aReleaseOnly)
+    return;
+
+  if (mListeningForChunk == static_cast<int64_t>(chunkIdx)) {
+    // We're already waiting for this chunk
+    LOG(("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
+         "chunk %lld [this=%p]", mListeningForChunk, this));
+
+    return;
+  }
+
+  rv = mFile->GetChunkLocked(chunkIdx, false, this, getter_AddRefs(mChunk));
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
+         "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv));
+
+  }
+  else if (!mChunk) {
+    mListeningForChunk = static_cast<int64_t>(chunkIdx);
+  }
+
+  MaybeNotifyListener();
+}
+
+void
+CacheFileInputStream::CanRead(int64_t *aCanRead, const char **aBuf)
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mChunk);
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
+  *aCanRead = mChunk->DataSize() - chunkOffset;
+  *aBuf = mChunk->BufForReading() + chunkOffset;
+
+  LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%lld]",
+       this, *aCanRead));
+}
+
+void
+CacheFileInputStream::NotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
+
+  MOZ_ASSERT(mCallback);
+
+  if (!mCallbackTarget)
+    mCallbackTarget = NS_GetCurrentThread();
+
+  nsCOMPtr<nsIInputStreamCallback> asyncCallback =
+    NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+
+  mCallback = nullptr;
+  mCallbackTarget = nullptr;
+
+  asyncCallback->OnInputStreamReady(this);
+}
+
+void
+CacheFileInputStream::MaybeNotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
+       "mClosed=%d, mStatus=0x%08x, mChunk=%p, mListeningForChunk=%lld, "
+       "mWaitingForUpdate=%d]", this, mCallback.get(), mClosed, mStatus,
+       mChunk.get(), mListeningForChunk, mWaitingForUpdate));
+
+  if (!mCallback)
+    return;
+
+  if (mClosed) {
+    NotifyListener();
+    return;
+  }
+
+  if (!mChunk) {
+    if (mListeningForChunk == -1) {
+      // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
+      NotifyListener();
+    }
+    return;
+  }
+
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  if (mWaitingForUpdate)
+    return;
+
+  int64_t canRead;
+  const char *buf;
+  CanRead(&canRead, &buf);
+
+  if (canRead > 0) {
+    if (!(mCallbackFlags & WAIT_CLOSURE_ONLY))
+      NotifyListener();
+  }
+  else if (canRead == 0) {
+    if (!mFile->mOutput) {
+      // EOF
+      NotifyListener();
+    }
+    else {
+      mChunk->WaitForUpdate(this);
+      mWaitingForUpdate = true;
+    }
+  }
+  else {
+    // Output have set EOF before mPos?
+    MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
+    NotifyListener();
+  }
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileInputStream__h__
+#define CacheFileInputStream__h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "CacheFileChunk.h"
+
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+
+class CacheFileInputStream : public nsIAsyncInputStream
+                           , public nsISeekableStream
+                           , public CacheFileChunkListener
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSIASYNCINPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
+
+public:
+  CacheFileInputStream(CacheFile *aFile);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+private:
+  virtual ~CacheFileInputStream();
+
+  void ReleaseChunk();
+  void EnsureCorrectChunk(bool aReleaseOnly);
+  void CanRead(int64_t *aCanRead, const char **aBuf);
+  void NotifyListener();
+  void MaybeNotifyListener();
+
+  nsRefPtr<CacheFile>      mFile;
+  nsRefPtr<CacheFileChunk> mChunk;
+  int64_t                  mPos;
+  bool                     mClosed;
+  nsresult                 mStatus;
+  bool                     mWaitingForUpdate;
+  int64_t                  mListeningForChunk;
+  bool                     mInReadSegments;
+
+  nsCOMPtr<nsIInputStreamCallback> mCallback;
+  uint32_t                         mCallbackFlags;
+  nsCOMPtr<nsIEventTarget>         mCallbackTarget;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -0,0 +1,682 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFileMetadata.h"
+
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+#include "CacheHashUtils.h"
+#include "CacheFileChunk.h"
+#include "../cache/nsCacheUtils.h"
+#include "mozilla/Telemetry.h"
+#include "prnetdb.h"
+
+
+namespace mozilla {
+namespace net {
+
+#define kMinMetadataRead 1024  // TODO find optimal value from telemetry
+#define kAlignSize       4096
+
+#define NO_EXPIRATION_TIME 0xFFFFFFFF
+
+NS_IMPL_ISUPPORTS1(CacheFileMetadata, CacheFileIOListener)
+
+CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey, bool aKeyIsHash)
+  : mHandle(aHandle)
+  , mKeyIsHash(aKeyIsHash)
+  , mHashArray(nullptr)
+  , mHashArraySize(0)
+  , mHashCount(0)
+  , mOffset(-1)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mWriteBuf(nullptr)
+  , mElementsSize(0)
+  , mIsDirty(false)
+{
+  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
+       this, aHandle, PromiseFlatCString(aKey).get()));
+
+  MOZ_COUNT_CTOR(CacheFileMetadata);
+  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mKey = aKey;
+}
+
+CacheFileMetadata::~CacheFileMetadata()
+{
+  LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
+
+  MOZ_COUNT_DTOR(CacheFileMetadata);
+  MOZ_ASSERT(!mListener);
+
+  if (mHashArray) {
+    free(mHashArray);
+    mHashArray = nullptr;
+    mHashArraySize = 0;
+  }
+
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+
+  DoMemoryReport(MemoryUsage());
+}
+
+CacheFileMetadata::CacheFileMetadata(const nsACString &aKey)
+  : mHandle(nullptr)
+  , mKeyIsHash(false)
+  , mHashArray(nullptr)
+  , mHashArraySize(0)
+  , mHashCount(0)
+  , mOffset(0)
+  , mBuf(nullptr)
+  , mBufSize(0)
+  , mWriteBuf(nullptr)
+  , mElementsSize(0)
+  , mIsDirty(true)
+{
+  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
+       this, PromiseFlatCString(aKey).get()));
+
+  MOZ_COUNT_CTOR(CacheFileMetadata);
+  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mMetaHdr.mFetchCount++;
+  mKey = aKey;
+  mMetaHdr.mKeySize = mKey.Length();
+}
+
+void
+CacheFileMetadata::SetHandle(CacheFileHandle *aHandle)
+{
+  LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
+
+  MOZ_ASSERT(!mHandle);
+
+  mHandle = aHandle;
+}
+
+nsresult
+CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener)
+{
+  LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this, aListener));
+
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mHashArray);
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(!mWriteBuf);
+
+  nsresult rv;
+
+  int64_t size = mHandle->FileSize();
+  MOZ_ASSERT(size != -1);
+
+  if (size == 0) {
+    // this is a new entry
+    LOG(("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
+         "metadata. [this=%p]", this));
+
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2*sizeof(uint32_t))) {
+    // there must be at least checksum, header and offset
+    LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
+         "empty metadata. [this=%p, filesize=%lld]", this, size));
+
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  // round offset to 4k blocks
+  int64_t offset = (size / kAlignSize) * kAlignSize;
+
+  if (size - offset < kMinMetadataRead && offset > kAlignSize)
+    offset -= kAlignSize;
+
+  mBufSize = size - offset;
+  mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
+
+  DoMemoryReport(MemoryUsage());
+
+  LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
+       "offset=%lld, filesize=%lld [this=%p]", offset, size, this));
+
+  mListener = aListener;
+  rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
+         " synchronously, creating empty metadata. [this=%p, rv=0x%08x]",
+         this, rv));
+
+    mListener = nullptr;
+    InitEmptyMetadata();
+    aListener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::WriteMetadata(uint32_t aOffset,
+                                 CacheFileMetadataListener *aListener)
+{
+  LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
+       this, aOffset, aListener));
+
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(!mWriteBuf);
+
+  nsresult rv;
+
+  mIsDirty = false;
+
+  mWriteBuf = static_cast<char *>(moz_xmalloc(sizeof(uint32_t) +
+                mHashCount * sizeof(CacheHashUtils::Hash16_t) +
+                sizeof(CacheFileMetadataHeader) + mKey.Length() + 1 +
+                mElementsSize + sizeof(uint32_t)));
+
+  char *p = mWriteBuf + sizeof(uint32_t);
+  memcpy(p, mHashArray, mHashCount * sizeof(CacheHashUtils::Hash16_t));
+  p += mHashCount * sizeof(CacheHashUtils::Hash16_t);
+  memcpy(p, &mMetaHdr, sizeof(CacheFileMetadataHeader));
+  p += sizeof(CacheFileMetadataHeader);
+  memcpy(p, mKey.get(), mKey.Length());
+  p += mKey.Length();
+  *p = 0;
+  p++;
+  memcpy(p, mBuf, mElementsSize);
+  p += mElementsSize;
+
+  CacheHashUtils::Hash32_t hash;
+  hash = CacheHashUtils::Hash(mWriteBuf + sizeof(uint32_t),
+                              p - mWriteBuf - sizeof(uint32_t));
+  *reinterpret_cast<uint32_t *>(mWriteBuf) = PR_htonl(hash);
+
+  *reinterpret_cast<uint32_t *>(p) = PR_htonl(aOffset);
+  p += sizeof(uint32_t);
+
+  mListener = aListener;
+  rv = CacheFileIOManager::Write(mHandle, aOffset, mWriteBuf, p - mWriteBuf,
+                                 true, this);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
+         "failed synchronously. [this=%p, rv=0x%08x]", this, rv));
+
+    mListener = nullptr;
+    free(mWriteBuf);
+    mWriteBuf = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+const char *
+CacheFileMetadata::GetElement(const char *aKey)
+{
+  const char *data = mBuf;
+  const char *limit = mBuf + mElementsSize;
+
+  while (data < limit) {
+    // Point to the value part
+    const char *value = data + strlen(data) + 1;
+    MOZ_ASSERT(value < limit, "Metadata elements corrupted");
+    if (strcmp(data, aKey) == 0) {
+      LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
+           this, aKey));
+      return value;
+    }
+
+    // Skip value part
+    data = value + strlen(value) + 1;
+  }
+  MOZ_ASSERT(data == limit, "Metadata elements corrupted");
+  LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
+       this, aKey));
+  return nullptr;
+}
+
+nsresult
+CacheFileMetadata::SetElement(const char *aKey, const char *aValue)
+{
+  LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]",
+       this, aKey, aValue));
+
+  MarkDirty();
+
+  const uint32_t keySize = strlen(aKey) + 1;
+  char *pos = const_cast<char *>(GetElement(aKey));
+
+  if (!aValue) {
+    // No value means remove the key/value pair completely, if existing
+    if (pos) {
+      uint32_t oldValueSize = strlen(pos) + 1;
+      uint32_t offset = pos - mBuf;
+      uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+      memmove(pos - keySize, pos + oldValueSize, remainder);
+      mElementsSize -= keySize + oldValueSize;
+    }
+    return NS_OK;
+  }
+
+  const uint32_t valueSize = strlen(aValue) + 1;
+  uint32_t newSize = mElementsSize + valueSize;
+  if (pos) {
+    const uint32_t oldValueSize = strlen(pos) + 1;
+    const uint32_t offset = pos - mBuf;
+    const uint32_t remainder = mElementsSize - (offset + oldValueSize);
+
+    // Update the value in place
+    newSize -= oldValueSize;
+    EnsureBuffer(newSize);
+
+    // Move the remainder to the right place
+    pos = mBuf + offset;
+    memmove(pos + valueSize, pos + oldValueSize, remainder);
+  } else {
+    // allocate new meta data element
+    newSize += keySize;
+    EnsureBuffer(newSize);
+
+    // Add after last element
+    pos = mBuf + mElementsSize;
+    memcpy(pos, aKey, keySize);
+    pos += keySize;
+  }
+
+  // Update value
+  memcpy(pos, aValue, valueSize);
+  mElementsSize = newSize;
+
+  return NS_OK;
+}
+
+CacheHashUtils::Hash16_t
+CacheFileMetadata::GetHash(uint32_t aIndex)
+{
+  MOZ_ASSERT(aIndex < mHashCount);
+  return PR_ntohs(mHashArray[aIndex]);
+}
+
+nsresult
+CacheFileMetadata::SetHash(uint32_t aIndex, CacheHashUtils::Hash16_t aHash)
+{
+  LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]",
+       this, aIndex, aHash));
+
+  MarkDirty();
+
+  MOZ_ASSERT(aIndex <= mHashCount);
+
+  if (aIndex > mHashCount) {
+    return NS_ERROR_INVALID_ARG;
+  } else if (aIndex == mHashCount) {
+    if ((aIndex + 1) * sizeof(CacheHashUtils::Hash16_t) > mHashArraySize) {
+      // reallocate hash array buffer
+      if (mHashArraySize == 0)
+        mHashArraySize = 32 * sizeof(CacheHashUtils::Hash16_t);
+      else
+        mHashArraySize *= 2;
+      mHashArray = static_cast<CacheHashUtils::Hash16_t *>(
+                     moz_xrealloc(mHashArray, mHashArraySize));
+    }
+
+    mHashCount++;
+  }
+
+  mHashArray[aIndex] = PR_htons(aHash);
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
+{
+  LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
+       this, aExpirationTime));
+
+  MarkDirty();
+  mMetaHdr.mExpirationTime = aExpirationTime;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetExpirationTime(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mExpirationTime;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::SetLastModified(uint32_t aLastModified)
+{
+  LOG(("CacheFileMetadata::SetLastModified() [this=%p, lastModified=%d]",
+       this, aLastModified));
+
+  MarkDirty();
+  mMetaHdr.mLastModified = aLastModified;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastModified(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mLastModified;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetLastFetched(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mLastFetched;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFetchCount(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mFetchCount;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileMetadata::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                                 nsresult aResult)
+{
+  LOG(("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mWriteBuf);
+
+  free(mWriteBuf);
+  mWriteBuf = nullptr;
+
+  nsCOMPtr<CacheFileMetadataListener> listener;
+
+  mListener.swap(listener);
+  listener->OnMetadataWritten(aResult);
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
+                              nsresult aResult)
+{
+  LOG(("CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08x]",
+       this, aHandle, aResult));
+
+  MOZ_ASSERT(mListener);
+
+  nsresult rv;
+  nsCOMPtr<CacheFileMetadataListener> listener;
+
+  if (NS_FAILED(aResult)) {
+    LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed, "
+         "creating empty metadata. [this=%p, rv=0x%08x]", this, aResult));
+
+    InitEmptyMetadata();
+    mListener.swap(listener);
+    listener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  // check whether we have read all necessary data
+  uint32_t realOffset = PR_ntohl(*(reinterpret_cast<uint32_t *>(
+                                 mBuf + mBufSize - sizeof(uint32_t))));
+
+  int64_t size = mHandle->FileSize();
+  MOZ_ASSERT(size != -1);
+
+  if (realOffset >= size) {
+    LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating empty "
+         "metadata. [this=%p, realOffset=%d, size=%lld]", this, realOffset,
+         size));
+
+    InitEmptyMetadata();
+    mListener.swap(listener);
+    listener->OnMetadataRead(NS_OK);
+    return NS_OK;
+  }
+
+  uint32_t usedOffset = size - mBufSize;
+
+  if (realOffset < usedOffset) {
+    uint32_t missing = usedOffset - realOffset;
+    // we need to read more data
+    mBuf = static_cast<char *>(moz_xrealloc(mBuf, mBufSize + missing));
+    memmove(mBuf + missing, mBuf, mBufSize);
+    mBufSize += missing;
+
+    DoMemoryReport(MemoryUsage());
+
+    LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
+         "have full metadata. [this=%p]", missing, this));
+
+    rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
+           " synchronously, creating empty metadata. [this=%p, rv=0x%08x]",
+           this, rv));
+
+      InitEmptyMetadata();
+      mListener.swap(listener);
+      listener->OnMetadataRead(rv);
+      return NS_OK;
+    }
+
+    return NS_OK;
+  }
+
+  // We have all data according to offset information at the end of the entry.
+  // Try to parse it.
+  rv = ParseMetadata(realOffset, realOffset - usedOffset);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
+         "empty metadata. [this=%p]", this));
+    InitEmptyMetadata();
+  }
+
+  mListener.swap(listener);
+  listener->OnMetadataRead(NS_OK);
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileMetadata::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
+{
+  MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+CacheFileMetadata::InitEmptyMetadata()
+{
+  if (mBuf) {
+    free(mBuf);
+    mBuf = nullptr;
+    mBufSize = 0;
+  }
+  mOffset = 0;
+  mMetaHdr.mFetchCount = 1;
+  mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME;
+  mMetaHdr.mKeySize = mKey.Length();
+
+  DoMemoryReport(MemoryUsage());
+}
+
+nsresult
+CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset)
+{
+  LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
+       "bufOffset=%d]", this, aMetaOffset, aBufOffset));
+
+  nsresult rv;
+
+  uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
+  uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
+  uint32_t hashCount = aMetaOffset / kChunkSize;
+  if (aMetaOffset % kChunkSize)
+    hashCount++;
+  uint32_t hashesLen = hashCount * sizeof(CacheHashUtils::Hash16_t);
+  uint32_t hdrOffset = hashesOffset + hashesLen;
+  uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
+
+  LOG(("CacheFileMetadata::ParseMetadata() [this=%p]\n  metaposOffset=%d\n  "
+       "hashesOffset=%d\n  hashCount=%d\n  hashesLen=%d\n  hdfOffset=%d\n  "
+       "keyOffset=%d\n", this, metaposOffset, hashesOffset, hashCount,
+       hashesLen,hdrOffset, keyOffset));
+
+  if (keyOffset > metaposOffset) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
+         this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  uint32_t elementsOffset = reinterpret_cast<CacheFileMetadataHeader *>(
+                              mBuf + hdrOffset)->mKeySize + keyOffset + 1;
+
+  if (elementsOffset > metaposOffset) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
+         "[this=%p]", elementsOffset, this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  // check that key ends with \0
+  if (mBuf[elementsOffset - 1] != 0) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
+         "[this=%p]", this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  if (!mKeyIsHash) {
+    uint32_t keySize = reinterpret_cast<CacheFileMetadataHeader *>(
+                         mBuf + hdrOffset)->mKeySize;
+
+    if (keySize != mKey.Length()) {
+      LOG(("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s [this=%p]",
+           nsCString(mBuf + keyOffset, keySize).get(), this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+
+    if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
+      LOG(("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s [this=%p]",
+           nsCString(mBuf + keyOffset, keySize).get(), this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+
+  // check metadata hash (data from hashesOffset to metaposOffset)
+  CacheHashUtils::Hash32_t hash;
+  hash = CacheHashUtils::Hash(mBuf + hashesOffset,
+                              metaposOffset - hashesOffset);
+
+  if (hash != PR_ntohl(*(reinterpret_cast<uint32_t *>(mBuf + aBufOffset)))) {
+    LOG(("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
+         "the metadata is %x, hash in file is %x [this=%p]", hash,
+         PR_ntohl(*(reinterpret_cast<uint32_t *>(mBuf + aBufOffset))), this));
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
+  // check elements
+  rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
+  if (NS_FAILED(rv))
+    return rv;
+
+  mHashArraySize = hashesLen;
+  mHashCount = hashCount;
+  if (mHashArraySize) {
+    mHashArray = static_cast<CacheHashUtils::Hash16_t *>(
+                   moz_xmalloc(mHashArraySize));
+    memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
+  }
+
+  memcpy(&mMetaHdr, mBuf + hdrOffset, sizeof(CacheFileMetadataHeader));
+  mMetaHdr.mFetchCount++;
+  MarkDirty();
+
+  mElementsSize = metaposOffset - elementsOffset;
+  memmove(mBuf, mBuf + elementsOffset, mElementsSize);
+  mOffset = aMetaOffset;
+
+  // TODO: shrink memory if buffer is too big
+
+  DoMemoryReport(MemoryUsage());
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize)
+{
+  if (aSize) {
+    // Check if the metadata ends with a zero byte.
+    if (aBuf[aSize - 1] != 0) {
+      NS_ERROR("Metadata elements are not null terminated");
+      LOG(("CacheFileMetadata::CheckElements() - Elements are not null "
+           "terminated. [this=%p]", this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+    // Check that there are an even number of zero bytes
+    // to match the pattern { key \0 value \0 }
+    bool odd = false;
+    for (uint32_t i = 0; i < aSize; i++) {
+      if (aBuf[i] == 0)
+        odd = !odd;
+    }
+    if (odd) {
+      NS_ERROR("Metadata elements are malformed");
+      LOG(("CacheFileMetadata::CheckElements() - Elements are malformed. "
+           "[this=%p]", this));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+  return NS_OK;
+}
+
+void
+CacheFileMetadata::EnsureBuffer(uint32_t aSize)
+{
+  if (mBufSize < aSize) {
+    mBufSize = aSize;
+    mBuf = static_cast<char *>(moz_xrealloc(mBuf, mBufSize));
+  }
+
+  DoMemoryReport(MemoryUsage());
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileMetadata__h__
+#define CacheFileMetadata__h__
+
+#include "CacheFileIOManager.h"
+#include "CacheStorageService.h"
+#include "CacheHashUtils.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+typedef struct {
+  uint32_t        mFetchCount;
+  uint32_t        mLastFetched;
+  uint32_t        mLastModified;
+  uint32_t        mExpirationTime;
+  uint32_t        mKeySize;
+} CacheFileMetadataHeader;
+
+#define CACHEFILEMETADATALISTENER_IID \
+{ /* a9e36125-3f01-4020-9540-9dafa8d31ba7 */       \
+  0xa9e36125,                                      \
+  0x3f01,                                          \
+  0x4020,                                          \
+  {0x95, 0x40, 0x9d, 0xaf, 0xa8, 0xd3, 0x1b, 0xa7} \
+}
+
+class CacheFileMetadataListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEMETADATALISTENER_IID)
+
+  NS_IMETHOD OnMetadataRead(nsresult aResult) = 0;
+  NS_IMETHOD OnMetadataWritten(nsresult aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileMetadataListener,
+                              CACHEFILEMETADATALISTENER_IID)
+
+
+class CacheFileMetadata : public CacheFileIOListener
+                        , public CacheMemoryConsumer
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  CacheFileMetadata(CacheFileHandle *aHandle,
+                    const nsACString &aKey,
+                    bool aKeyIsHash);
+  CacheFileMetadata(const nsACString &aKey);
+
+  void SetHandle(CacheFileHandle *aHandle);
+
+  nsresult ReadMetadata(CacheFileMetadataListener *aListener);
+  nsresult WriteMetadata(uint32_t aOffset,
+                         CacheFileMetadataListener *aListener);
+
+  const char * GetElement(const char *aKey);
+  nsresult     SetElement(const char *aKey, const char *aValue);
+
+  CacheHashUtils::Hash16_t GetHash(uint32_t aIndex);
+  nsresult                 SetHash(uint32_t aIndex,
+                                   CacheHashUtils::Hash16_t aHash);
+
+  nsresult SetExpirationTime(uint32_t aExpirationTime);
+  nsresult GetExpirationTime(uint32_t *_retval);
+  nsresult SetLastModified(uint32_t aLastModified);
+  nsresult GetLastModified(uint32_t *_retval);
+  nsresult GetLastFetched(uint32_t *_retval);
+  nsresult GetFetchCount(uint32_t *_retval);
+
+  int64_t  Offset() { return mOffset; }
+  uint32_t ElementsSize() { return mElementsSize; }
+  void     MarkDirty() { mIsDirty = true; }
+  bool     IsDirty() { return mIsDirty; }
+  uint32_t MemoryUsage() { return mHashArraySize + mBufSize; }
+
+  NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
+                           nsresult aResult);
+  NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
+  NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
+  NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
+
+private:
+  virtual ~CacheFileMetadata();
+
+  void     InitEmptyMetadata();
+  nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset);
+  nsresult CheckElements(const char *aBuf, uint32_t aSize);
+  void     EnsureBuffer(uint32_t aSize);
+
+  nsRefPtr<CacheFileHandle>           mHandle;
+  nsCString                           mKey;
+  bool                                mKeyIsHash;
+  CacheHashUtils::Hash16_t           *mHashArray;
+  uint32_t                            mHashArraySize;
+  uint32_t                            mHashCount;
+  int64_t                             mOffset;
+  char                               *mBuf; // used for parsing, then points
+                                            // to elements
+  uint32_t                            mBufSize;
+  char                               *mWriteBuf;
+  CacheFileMetadataHeader             mMetaHdr;
+  uint32_t                            mElementsSize;
+  bool                                mIsDirty;
+  nsCOMPtr<CacheFileMetadataListener> mListener;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -0,0 +1,382 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheFileOutputStream.h"
+
+#include "CacheLog.h"
+#include "CacheFile.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(CacheFileOutputStream)
+NS_IMETHODIMP_(nsrefcnt)
+CacheFileOutputStream::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    {
+      CacheFileAutoLock lock(mFile);
+      mFile->RemoveOutput(this);
+    }
+    delete (this);
+    return 0;
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
+  NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+  NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile)
+  : mFile(aFile)
+  , mPos(0)
+  , mClosed(false)
+  , mStatus(NS_OK)
+  , mCallbackFlags(0)
+{
+  LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
+  MOZ_COUNT_CTOR(CacheFileOutputStream);
+}
+
+CacheFileOutputStream::~CacheFileOutputStream()
+{
+  LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
+  MOZ_COUNT_DTOR(CacheFileOutputStream);
+}
+
+// nsIOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::Close()
+{
+  LOG(("CacheFileOutputStream::Close() [this=%p]", this));
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Flush()
+{
+  // TODO do we need to implement flush ???
+  LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount,
+                             uint32_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
+
+  if (mClosed) {
+    LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
+         "status=0x%08x]", this, mStatus));
+
+    return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
+  }
+
+  *_retval = aCount;
+
+  while (aCount) {
+    EnsureCorrectChunk(false);
+
+    FillHole();
+
+    uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
+    uint32_t canWrite = kChunkSize - chunkOffset;
+    uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
+    mChunk->EnsureBufSize(chunkOffset + thisWrite);
+    memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite);
+
+    mPos += thisWrite;
+    aBuf += thisWrite;
+    aCount -= thisWrite;
+
+    mChunk->UpdateDataSize(chunkOffset, thisWrite, false);
+  }
+
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]",
+       *_retval, this));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
+                                 uint32_t *_retval)
+{
+  LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
+       ", count=%d]", this, aFromStream, aCount));
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure,
+                                     uint32_t aCount, uint32_t *_retval)
+{
+  LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
+       "count=%d]", this, aCount));
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::IsNonBlocking(bool *_retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+// nsIAsyncOutputStream
+NS_IMETHODIMP
+CacheFileOutputStream::CloseWithStatus(nsresult aStatus)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]",
+       this, aStatus));
+
+  if (mClosed) {
+    MOZ_ASSERT(!mCallback);
+    return NS_OK;
+  }
+
+  mClosed = true;
+  mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
+
+  if (mChunk)
+    ReleaseChunk();
+
+  if (mCallback)
+    NotifyListener();
+
+  mFile->RemoveOutput(this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
+                                 uint32_t aFlags,
+                                 uint32_t aRequestedCount,
+                                 nsIEventTarget *aEventTarget)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
+       "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
+       aRequestedCount, aEventTarget));
+
+  mCallback = aCallback;
+  mCallbackFlags = aFlags;
+
+  if (!mCallback)
+    return NS_OK;
+
+  // The stream is blocking so it is writable at any time
+  if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY))
+    NotifyListener();
+
+  return NS_OK;
+}
+
+// nsISeekableStream
+NS_IMETHODIMP
+CacheFileOutputStream::Seek(int32_t whence, int64_t offset)
+{
+  CacheFileAutoLock lock(mFile);
+
+  LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%lld]",
+       this, whence, offset));
+
+  if (mClosed) {
+    LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  int64_t newPos = offset;
+  switch (whence) {
+    case NS_SEEK_SET:
+      break;
+    case NS_SEEK_CUR:
+      newPos += mPos;
+      break;
+    case NS_SEEK_END:
+      newPos += mFile->mDataSize;
+      break;
+    default:
+      NS_ERROR("invalid whence");
+      return NS_ERROR_INVALID_ARG;
+  }
+  mPos = newPos;
+  EnsureCorrectChunk(true);
+
+  LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%lld]", this, mPos));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::Tell(int64_t *_retval)
+{
+  CacheFileAutoLock lock(mFile);
+
+  if (mClosed) {
+    LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  *_retval = mPos;
+
+  LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%lld]", this, *_retval));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileOutputStream::SetEOF()
+{
+  MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
+  // Right now we don't use SetEOF(). If we ever need this method, we need
+  // to think about what to do with input streams that already points beyond
+  // new EOF.
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// CacheFileChunkListener
+nsresult
+CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkWritten should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
+                                        uint32_t aChunkIdx,
+                                        CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkAvailable should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk)
+{
+  MOZ_CRASH(
+    "CacheFileOutputStream::OnChunkUpdated should not be called!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+CacheFileOutputStream::ReleaseChunk()
+{
+  LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]",
+       this, mChunk->Index()));
+
+  mFile->ReleaseOutsideLock(mChunk.forget().get());
+}
+
+void
+CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly)
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
+       this, aReleaseOnly));
+
+  uint32_t chunkIdx = mPos / kChunkSize;
+
+  if (mChunk) {
+    if (mChunk->Index() == chunkIdx) {
+      // we have a correct chunk
+      LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
+           "[this=%p, idx=%d]", this, chunkIdx));
+
+      return;
+    }
+    else {
+      ReleaseChunk();
+    }
+  }
+
+  if (aReleaseOnly)
+    return;
+
+  DebugOnly<nsresult> rv;
+  rv = mFile->GetChunkLocked(chunkIdx, true, nullptr, getter_AddRefs(mChunk));
+  MOZ_ASSERT(NS_SUCCEEDED(rv),
+             "CacheFile::GetChunkLocked() should always succeed for writer");
+}
+
+void
+CacheFileOutputStream::FillHole()
+{
+  mFile->AssertOwnsLock();
+
+  MOZ_ASSERT(mChunk);
+  MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
+
+  uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
+  if (mChunk->DataSize() >= pos)
+    return;
+
+  LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
+       "%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this));
+
+  mChunk->EnsureBufSize(pos);
+  memset(mChunk->BufForWriting() + mChunk->DataSize(), 0,
+         pos - mChunk->DataSize());
+
+  mChunk->UpdateDataSize(mChunk->DataSize(), pos - mChunk->DataSize(), false);
+}
+
+void
+CacheFileOutputStream::NotifyListener()
+{
+  mFile->AssertOwnsLock();
+
+  LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
+
+  MOZ_ASSERT(mCallback);
+
+  if (!mCallbackTarget)
+    mCallbackTarget = NS_GetCurrentThread();
+
+  nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
+    NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
+
+  mCallback = nullptr;
+  mCallbackTarget = nullptr;
+
+  asyncCallback->OnOutputStreamReady(this);
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheFileOutputStream__h__
+#define CacheFileOutputStream__h__
+
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "CacheFileChunk.h"
+
+
+namespace mozilla {
+namespace net {
+
+class CacheFile;
+
+class CacheFileOutputStream : public nsIAsyncOutputStream
+                            , public nsISeekableStream
+                            , public CacheFileChunkListener
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+  NS_DECL_NSIASYNCOUTPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
+
+public:
+  CacheFileOutputStream(CacheFile *aFile);
+
+  NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+                              CacheFileChunk *aChunk);
+  NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
+
+private:
+  virtual ~CacheFileOutputStream();
+
+  void ReleaseChunk();
+  void EnsureCorrectChunk(bool aReleaseOnly);
+  void FillHole();
+  void NotifyListener();
+
+  nsRefPtr<CacheFile>      mFile;
+  nsRefPtr<CacheFileChunk> mChunk;
+  int64_t                  mPos;
+  bool                     mClosed;
+  nsresult                 mStatus;
+
+  nsCOMPtr<nsIOutputStreamCallback> mCallback;
+  uint32_t                          mCallbackFlags;
+  nsCOMPtr<nsIEventTarget>          mCallbackTarget;
+};
+
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheHashUtils.h"
+
+#include "plstr.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ *  CacheHashUtils::Hash(const char * key, uint32_t initval)
+ *
+ *  See http://burtleburtle.net/bob/hash/evahash.html for more information
+ *  about this hash function.
+ *
+ *  This algorithm is used to check the data integrity.
+ */
+
+static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
+{
+  a -= b; a -= c; a ^= (c>>13);
+  b -= c; b -= a; b ^= (a<<8);
+  c -= a; c -= b; c ^= (b>>13);
+  a -= b; a -= c; a ^= (c>>12);
+  b -= c; b -= a; b ^= (a<<16);
+  c -= a; c -= b; c ^= (b>>5);
+  a -= b; a -= c; a ^= (c>>3);
+  b -= c; b -= a; b ^= (a<<10);
+  c -= a; c -= b; c ^= (b>>15);
+}
+
+CacheHashUtils::Hash32_t
+CacheHashUtils::Hash(const char *aData, uint32_t aSize, uint32_t aInitval)
+{
+  const uint8_t *k = reinterpret_cast<const uint8_t*>(aData);
+  uint32_t a, b, c, len/*, length*/;
+
+//  length = PL_strlen(key);
+  /* Set up the internal state */
+  len = aSize;
+  a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
+  c = aInitval;        /* variable initialization of internal state */
+
+  /*---------------------------------------- handle most of the key */
+  while (len >= 12)
+  {
+    a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
+    b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
+    c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
+    hashmix(a, b, c);
+    k += 12; len -= 12;
+  }
+
+  /*------------------------------------- handle the last 11 bytes */
+  c += aSize;
+  switch(len) {              /* all the case statements fall through */
+    case 11: c += (uint32_t(k[10])<<24);
+    case 10: c += (uint32_t(k[9])<<16);
+    case 9 : c += (uint32_t(k[8])<<8);
+    /* the low-order byte of c is reserved for the length */
+    case 8 : b += (uint32_t(k[7])<<24);
+    case 7 : b += (uint32_t(k[6])<<16);
+    case 6 : b += (uint32_t(k[5])<<8);
+    case 5 : b += k[4];
+    case 4 : a += (uint32_t(k[3])<<24);
+    case 3 : a += (uint32_t(k[2])<<16);
+    case 2 : a += (uint32_t(k[1])<<8);
+    case 1 : a += k[0];
+    /* case 0: nothing left to add */
+  }
+  hashmix(a, b, c);
+
+  return c;
+}
+
+CacheHashUtils::Hash16_t
+CacheHashUtils::Hash16(const char *aData, uint32_t aSize, uint32_t aInitval)
+{
+  Hash32_t hash = Hash(aData, aSize, aInitval);
+  return (hash & 0xFFFF);
+}
+
+} // net
+} // mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheHashUtils__h__
+#define CacheHashUtils__h__
+
+#include "mozilla/Types.h"
+#include "prnetdb.h"
+#include "nsPrintfCString.h"
+
+#define LOGSHA1(x) \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[0]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[1]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[2]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[3]), \
+    PR_htonl((reinterpret_cast<const uint32_t *>(x))[4])
+
+#define SHA1STRING(x) \
+    (nsPrintfCString("%08x%08x%08x%08x%08x", LOGSHA1(x)).get())
+
+namespace mozilla {
+namespace net {
+
+class CacheHashUtils
+{
+public:
+  typedef uint16_t Hash16_t;
+  typedef uint32_t Hash32_t;
+
+  static Hash32_t Hash(const char* aData, uint32_t aSize, uint32_t aInitval=0);
+  static Hash16_t Hash16(const char* aData, uint32_t aSize,
+                         uint32_t aInitval=0);
+};
+
+
+} // net
+} // mozilla
+
+#endif