Back out fix for bug 405407 due to regressions.
authorJosh Aas <joshmoz@gmail.com>
Mon, 19 Nov 2012 18:02:21 -0500
changeset 119296 452ecece0b175266f88d678f191386bfb9810411
parent 119295 bd4d746598fd8a07b8df8e89d10c6cce75f6c346
child 119297 35c328e037959f22d37eba987d7b763829297a0d
push idunknown
push userunknown
push dateunknown
bugs405407
milestone20.0a1
Back out fix for bug 405407 due to regressions.
netwerk/cache/nsCacheEntryDescriptor.h
netwerk/cache/nsDiskCacheStreams.cpp
netwerk/cache/nsDiskCacheStreams.h
toolkit/components/telemetry/Histograms.json
--- a/netwerk/cache/nsCacheEntryDescriptor.h
+++ b/netwerk/cache/nsCacheEntryDescriptor.h
@@ -170,17 +170,16 @@ private:
          }
          virtual ~nsOutputStreamWrapper()
          { 
              // XXX _HACK_ the storage stream needs this!
              Close();
              {
              nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSE));
              mDescriptor->mOutput = nullptr;
-             mOutput = nullptr;
              }
              NS_RELEASE(mDescriptor);
          }
 
      private:
          nsresult LazyInit();
          nsresult EnsureInit() { return mInitialized ? NS_OK : LazyInit(); }
          nsresult OnWrite(uint32_t count);
--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -10,26 +10,24 @@
 #include "nsDiskCacheStreams.h"
 #include "nsCacheService.h"
 #include "mozilla/FileUtils.h"
 #include "nsIDiskCacheStreamInternal.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 
+
+
 // Assumptions:
 //      - cache descriptors live for life of streams
 //      - streams will only be used by FileTransport,
 //         they will not be directly accessible to clients
 //      - overlapped I/O is NOT supported
 
-// we pick 16k as the max buffer size because that is the threshold above which
-//      we are unable to store the data in the cache block files
-//      see nsDiskCacheMap.[cpp,h]
-#define kMaxBufferSize      (16 * 1024)
 
 /******************************************************************************
  *  nsDiskCacheInputStream
  *****************************************************************************/
 class nsDiskCacheInputStream : public nsIInputStream {
 
 public:
 
@@ -175,109 +173,218 @@ nsDiskCacheInputStream::ReadSegments(nsW
 
 NS_IMETHODIMP
 nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
 {
     *nonBlocking = false;
     return NS_OK;
 }
 
+
+/******************************************************************************
+ *  nsDiskCacheOutputStream
+ *****************************************************************************/
+class nsDiskCacheOutputStream : public nsIOutputStream
+                              , public nsIDiskCacheStreamInternal
+{
+public:
+    nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
+    virtual ~nsDiskCacheOutputStream();
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOUTPUTSTREAM
+    NS_DECL_NSIDISKCACHESTREAMINTERNAL
+
+    void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
+
+private:
+    nsDiskCacheStreamIO *           mStreamIO;  // backpointer to parent
+    bool                            mClosed;
+};
+
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheOutputStream,
+                              nsIOutputStream,
+                              nsIDiskCacheStreamInternal)
+
+nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
+    : mStreamIO(parent)
+    , mClosed(false)
+{
+    NS_ADDREF(mStreamIO);
+}
+
+
+nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
+{
+    Close();
+    ReleaseStreamIO();
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Close()
+{
+    nsresult rv = NS_OK;
+    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+    if (!mClosed) {
+        mClosed = true;
+        // tell parent streamIO we are closing
+        rv = mStreamIO->CloseOutputStream(this);
+    }
+
+    mozilla::Telemetry::ID id;
+    if (NS_IsMainThread())
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
+    else
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
+
+    mozilla::Telemetry::AccumulateTimeDelta(id, start);
+
+    return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::CloseInternal()
+{
+    nsresult rv = NS_OK;
+    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+    if (!mClosed) {
+        mClosed = true;
+        // tell parent streamIO we are closing
+        rv = mStreamIO->CloseOutputStreamInternal(this);
+    }
+
+    mozilla::Telemetry::ID id;
+    if (NS_IsMainThread())
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
+    else
+        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
+
+    mozilla::Telemetry::AccumulateTimeDelta(id, start);
+
+    return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Flush()
+{
+    if (mClosed)  return NS_BASE_STREAM_CLOSED;
+    // yeah, yeah, well get to it...eventually...
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::Write(const char *buf, uint32_t count, uint32_t *bytesWritten)
+{
+    if (mClosed)  return NS_BASE_STREAM_CLOSED;
+    return mStreamIO->Write(buf, count, bytesWritten);
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
+{
+    NS_NOTREACHED("WriteFrom");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
+                                        void *           closure,
+                                        uint32_t         count,
+                                        uint32_t *       bytesWritten)
+{
+    NS_NOTREACHED("WriteSegments");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
+{
+    *nonBlocking = false;
+    return NS_OK;
+}
+
+
+
 /******************************************************************************
  *  nsDiskCacheStreamIO
  *****************************************************************************/
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheStreamIO, nsIOutputStream, nsIDiskCacheStreamInternal)
+NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
+
+// we pick 16k as the max buffer size because that is the threshold above which
+//      we are unable to store the data in the cache block files
+//      see nsDiskCacheMap.[cpp,h]
+#define kMaxBufferSize      (16 * 1024)
 
 nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding *   binding)
     : mBinding(binding)
+    , mOutStream(nullptr)
     , mInStreamCount(0)
     , mFD(nullptr)
     , mStreamPos(0)
     , mStreamEnd(0)
     , mBufPos(0)
     , mBufEnd(0)
     , mBufSize(0)
     , mBufDirty(false)
-    , mOutputStreamIsOpen(false)
     , mBuffer(nullptr)
 {
     mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
 
     // acquire "death grip" on cache service
     nsCacheService *service = nsCacheService::GlobalInstance();
     NS_ADDREF(service);
 }
 
 
 nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
 {
-    if (mOutputStreamIsOpen) {
-        nsCacheService::AssertOwnsLock();
-        CloseInternal();
-    }
-
-    NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
-    NS_ASSERTION(mInStreamCount == 0, "input stream still open");
-    NS_ASSERTION(!mFD, "file descriptor not closed");
-
-    DeleteBuffer();
+    Close();
 
     // release "death grip" on cache service
     nsCacheService *service = nsCacheService::GlobalInstance();
     NS_RELEASE(service);
 }
 
 
-NS_IMETHODIMP
-nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
-{
-    NS_NOTREACHED("WriteFrom");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::WriteSegments(nsReadSegmentFun reader,
-                                       void *           closure,
-                                       uint32_t         count,
-                                       uint32_t *       bytesWritten)
-{
-    NS_NOTREACHED("WriteSegments");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
-{
-  *nonBlocking = false;
-  return NS_OK;
-}
-
-
-NS_IMETHODIMP
+void
 nsDiskCacheStreamIO::Close()
 {
-    nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM));
-    return CloseInternal();
+    // this should only be called from our destructor
+    // no one is interested in us anymore, so we don't need to grab any locks
+    
+    // assert streams closed
+    NS_ASSERTION(!mOutStream, "output stream still open");
+    NS_ASSERTION(mInStreamCount == 0, "input stream still open");
+    NS_ASSERTION(!mFD, "file descriptor not closed");
+
+    DeleteBuffer();
 }
 
 
 // NOTE: called with service lock held
 nsresult
 nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
 {
     NS_ENSURE_ARG_POINTER(inputStream);
     NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
 
     *inputStream = nullptr;
     
     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
 
-    if (mOutputStreamIsOpen) {
-        NS_WARNING("already have the output stream open");
+    if (mOutStream) {
+        NS_WARNING("already have an output stream open");
         return NS_ERROR_NOT_AVAILABLE;
     }
 
     nsresult            rv;
     PRFileDesc *        fd = nullptr;
 
     mStreamEnd = mBinding->mCacheEntry->DataSize();
     if (mStreamEnd == 0) {
@@ -311,118 +418,90 @@ nsDiskCacheStreamIO::GetInputStream(uint
 // NOTE: called with service lock held
 nsresult
 nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
 {
     NS_ENSURE_ARG_POINTER(outputStream);
     *outputStream = nullptr;
 
     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
-
-    NS_ASSERTION(!mOutputStreamIsOpen, "already have the output stream open");
+        
+    NS_ASSERTION(!mOutStream, "already have an output stream open");
     NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
-    if (mOutputStreamIsOpen || mInStreamCount)  return NS_ERROR_NOT_AVAILABLE;
+    if (mOutStream || mInStreamCount)  return NS_ERROR_NOT_AVAILABLE;
     
     // mBuffer lazily allocated, but might exist if a previous stream already
     // created one.
     mBufPos    = 0;
     mStreamPos = 0;
     mStreamEnd = mBinding->mCacheEntry->DataSize();
 
-    if (offset > mStreamEnd) {
-        NS_WARNING("seek offset out of range");
-        return NS_ERROR_INVALID_ARG;
+    nsresult rv;
+    if (offset) {
+        rv = Seek(PR_SEEK_SET, offset);
+        if (NS_FAILED(rv)) return rv;
     }
-
-    nsresult rv;
-    // Seek and truncate at the desired offset
-    if (mBinding->mRecord.DataLocationInitialized() &&
-        (mBinding->mRecord.DataFile() == 0)) {
-        // File storage, seek in file
-        rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (offset) {
-            int32_t newPos = PR_Seek(mFD, offset, PR_SEEK_SET);
-            if (newPos == -1) {
-                return NS_ErrorAccordingToNSPR();
-            }
-        }
+    rv = SetEOF();
+    if (NS_FAILED(rv)) return rv;
 
-        // Truncate at start position (offset)
-        rv = nsDiskCache::Truncate(mFD, offset);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        mStreamPos = mStreamEnd = offset;
-        UpdateFileSize();
-    } else if (offset) {
-        // else, read and seek in mBuffer
-        rv = ReadCacheBlocks();
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        // Start writing at the provided offset
-        mBufEnd = mBufPos = offset;
-        mStreamPos = mStreamEnd = offset;
-    }
-
-    mOutputStreamIsOpen = true;
-    // return myself as the output stream
-    NS_ADDREF(*outputStream = this);
+    // create a new output stream
+    mOutStream = new nsDiskCacheOutputStream(this);
+    if (!mOutStream)  return NS_ERROR_OUT_OF_MEMORY;
+    
+    NS_ADDREF(*outputStream = mOutStream);
     return NS_OK;
 }
 
-
 nsresult
 nsDiskCacheStreamIO::ClearBinding()
 {
     nsresult rv = NS_OK;
-    if (mBinding && mOutputStreamIsOpen)
+    if (mBinding && mOutStream)
         rv = Flush();
     mBinding = nullptr;
     return rv;
 }
 
-
-NS_IMETHODIMP
-nsDiskCacheStreamIO::CloseInternal()
+nsresult
+nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream *  outputStream)
 {
-    mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+    nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
+    return CloseOutputStreamInternal(outputStream);
+}
 
-    if (mOutputStreamIsOpen) {
-        if (!mBinding) {    // if we're severed, just clear member variables
-            NS_ASSERTION(!mBufDirty, "oops");
-        } else {
-            nsresult rv = Flush();
-            NS_ENSURE_SUCCESS(rv, rv);
-        }
-        mOutputStreamIsOpen = PR_FALSE;
+nsresult
+nsDiskCacheStreamIO::CloseOutputStreamInternal(
+    nsDiskCacheOutputStream * outputStream)
+{
+    nsresult   rv;
+
+    if (outputStream != mOutStream) {
+        NS_WARNING("mismatched output streams");
+        return NS_ERROR_UNEXPECTED;
+    }
+    
+    // output stream is closing
+    if (!mBinding) {    // if we're severed, just clear member variables
+        NS_ASSERTION(!mBufDirty, "oops");
+        mOutStream = nullptr;
+        outputStream->ReleaseStreamIO();
+        return NS_ERROR_NOT_AVAILABLE;
     }
 
-    // Make sure to always close the FileDescriptor
-    if (mFD) {
-        (void) PR_Close(mFD);
-        mFD = nullptr;
-    }
+    rv = Flush();
+    if (NS_FAILED(rv))
+        NS_WARNING("Flush() failed");
 
-    mozilla::Telemetry::ID id;
-    if (NS_IsMainThread())
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
-    else
-        id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
-
-    mozilla::Telemetry::AccumulateTimeDelta(id, start);
-
-    return NS_OK;
+    mOutStream = nullptr;
+    return rv;
 }
 
-
-NS_IMETHODIMP
+nsresult
 nsDiskCacheStreamIO::Flush()
 {
-    if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
-
     NS_ASSERTION(mBinding, "oops");
 
     CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
         mBinding->mRecord.HashNumber(), mBinding->mDoomed));
 
     if (!mBufDirty) {
         if (mFD) {
             (void) PR_Close(mFD);
@@ -509,25 +588,22 @@ nsDiskCacheStreamIO::Flush()
 }
 
 
 // assumptions:
 //      only one thread writing at a time
 //      never have both output and input streams open
 //      OnDataSizeChanged() will have already been called to update entry->DataSize()
 
-NS_IMETHODIMP
+nsresult
 nsDiskCacheStreamIO::Write( const char * buffer,
                             uint32_t     count,
                             uint32_t *   bytesWritten)
 {
-    if (!mOutputStreamIsOpen) {
-        return NS_BASE_STREAM_CLOSED;
-    }
-
+    nsresult    rv = NS_OK;
     nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE)); // grab service lock
     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
 
     if (mInStreamCount) {
         // we have open input streams already
         // this is an error until we support overlapped I/O
         NS_WARNING("Attempting to write to cache entry with open input streams.\n");
         return NS_ERROR_NOT_AVAILABLE;
@@ -584,17 +660,17 @@ nsDiskCacheStreamIO::Write( const char *
         NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
 
         // If we have flushed to a file, update the file size
         if (flushed && mFD) {
             UpdateFileSize();
         }
     }
     
-    return NS_OK;
+    return rv;
 }
 
 
 void
 nsDiskCacheStreamIO::UpdateFileSize()
 {
     NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
     
@@ -693,17 +769,17 @@ nsDiskCacheStreamIO::FlushBufferToFile()
             // remove cache block storage
             nsDiskCacheMap * cacheMap = mDevice->CacheMap();
             rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
             if (NS_FAILED(rv))  return rv;
         }
         record->SetDataFileGeneration(mBinding->mGeneration);
         
         // allocate file
-        rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
+        rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
         if (NS_FAILED(rv))  return rv;
 
         int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
         if (dataSize != -1)
             mozilla::fallocate(mFD, NS_MIN<int64_t>(dataSize, kPreallocateLimit));
     }
     
     // write buffer
@@ -741,8 +817,163 @@ nsDiskCacheStreamIO::SizeOfIncludingThis
     size_t usage = aMallocSizeOf(this);
 
     usage += aMallocSizeOf(mLocalFile);
     usage += aMallocSizeOf(mFD);
     usage += aMallocSizeOf(mBuffer);
 
     return usage;
 }
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::Seek(int32_t whence, int32_t offset)
+{
+    int32_t  newPos;
+    if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
+    
+    if (uint32_t(offset) > mStreamEnd)  return NS_ERROR_FAILURE;
+    
+    if (mBinding->mRecord.DataLocationInitialized()) {
+        if (mBinding->mRecord.DataFile() == 0) {
+            if (!mFD) {
+                // we need an mFD, we better open it now
+                nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+                if (NS_FAILED(rv))  return rv;
+            }
+        }
+    }
+    
+    if (mFD) {
+        // do we have data in the buffer that needs to be flushed?
+        if (mBufDirty) {
+            // XXX optimization: are we just moving within the current buffer?
+            nsresult rv = FlushBufferToFile();
+            if (NS_FAILED(rv))  return rv;
+        }
+        
+        newPos = PR_Seek(mFD, offset, (PRSeekWhence)whence);
+        if (newPos == -1)
+            return NS_ErrorAccordingToNSPR();
+        
+        mStreamPos = (uint32_t) newPos;
+        mBufPos = 0;
+        mBufEnd = 0;
+        return NS_OK;
+    }
+    
+    // else, seek in mBuffer
+    
+    switch(whence) {
+        case PR_SEEK_SET:
+            newPos = offset;
+            break;
+            
+        case PR_SEEK_CUR:   // relative from current posistion
+            newPos = offset + (uint32_t)mStreamPos;
+            break;
+            
+        case PR_SEEK_END:   // relative from end
+            newPos = offset + (uint32_t)mBufEnd;
+            break;
+            
+        default:
+            return NS_ERROR_INVALID_ARG;
+    }
+    
+    // read data into mBuffer if not read yet.
+    if (mStreamEnd && !mBufEnd) {
+        if (newPos > 0) {
+            nsresult rv = ReadCacheBlocks();
+            if (NS_FAILED(rv))  return rv;
+        }
+    }
+    
+    // stream buffer sanity checks
+    NS_ASSERTION(mBufEnd <= kMaxBufferSize, "bad stream");
+    NS_ASSERTION(mBufPos <= mBufEnd,     "bad stream");
+    NS_ASSERTION(mStreamPos == mBufPos,  "bad stream");
+    NS_ASSERTION(mStreamEnd == mBufEnd,  "bad stream");
+    
+    if ((newPos < 0) || (uint32_t(newPos) > mBufEnd)) {
+        NS_WARNING("seek offset out of range");
+        return NS_ERROR_INVALID_ARG;
+    }
+    
+    mStreamPos = newPos;
+    mBufPos    = newPos;
+    return NS_OK;
+}
+
+
+// called only from nsDiskCacheOutputStream::Tell
+nsresult
+nsDiskCacheStreamIO::Tell(uint32_t * result)
+{
+    NS_ENSURE_ARG_POINTER(result);
+    *result = mStreamPos;
+    return NS_OK;
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::SetEOF()
+{
+    nsresult    rv;
+    bool        needToCloseFD = false;
+    
+    NS_ASSERTION(mStreamPos <= mStreamEnd, "bad stream");
+    if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
+    
+    if (mBinding->mRecord.DataLocationInitialized()) {
+        if (mBinding->mRecord.DataFile() == 0) {
+            if (!mFD) {
+                // we need an mFD, we better open it now
+                rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+                if (NS_FAILED(rv))  return rv;
+                needToCloseFD = true;
+            }
+        } else {
+            // data in cache block files
+            if ((mStreamPos != 0) && (mStreamPos != mBufPos)) {
+                // only read data if there will be some left after truncation
+                rv = ReadCacheBlocks();
+                if (NS_FAILED(rv))  return rv;
+            }
+            
+            // We need to make sure we reflect this change in Flush().
+            // In particular, if mStreamPos is 0 and we never write to
+            // the buffer, we want the storage to be deleted.
+            mBufDirty = true;
+        }
+    }
+    
+    if (mFD) {
+        rv = nsDiskCache::Truncate(mFD, mStreamPos);
+#ifdef DEBUG
+        uint32_t oldSizeK = (mStreamEnd + 0x03FF) >> 10;
+        NS_ASSERTION(mBinding->mRecord.DataFileSize() == oldSizeK, "bad disk cache entry size");
+    } else {
+        // data stored in buffer.
+        NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "buffer truncation inadequate");
+        NS_ASSERTION(mBufPos == mStreamPos, "bad stream");
+        NS_ASSERTION(mBuffer ? mBufEnd == mStreamEnd : true, "bad stream");
+#endif
+    }
+
+    NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "cache entry not updated");
+    // we expect nsCacheEntryDescriptor::TransportWrapper::OpenOutputStream()
+    // to eventually update the cache entry
+
+    mStreamEnd  = mStreamPos;
+    mBufEnd     = mBufPos;
+
+    if (mFD) {
+        UpdateFileSize();
+        if (needToCloseFD) {
+            (void) PR_Close(mFD);
+            mFD = nullptr;
+        }
+    }
+
+    return  NS_OK;
+}
--- a/netwerk/cache/nsDiskCacheStreams.h
+++ b/netwerk/cache/nsDiskCacheStreams.h
@@ -9,35 +9,40 @@
 #define _nsDiskCacheStreams_h_
 
 #include "nsDiskCacheBinding.h"
 
 #include "nsCache.h"
 
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
-#include "nsIDiskCacheStreamInternal.h"
 
 #include "pratom.h"
 
 class nsDiskCacheInputStream;
+class nsDiskCacheOutputStream;
 class nsDiskCacheDevice;
 
-class nsDiskCacheStreamIO : public nsIOutputStream, nsIDiskCacheStreamInternal {
+class nsDiskCacheStreamIO : public nsISupports {
 public:
              nsDiskCacheStreamIO(nsDiskCacheBinding *   binding);
     virtual ~nsDiskCacheStreamIO();
     
     NS_DECL_ISUPPORTS
-    NS_DECL_NSIOUTPUTSTREAM
-    NS_DECL_NSIDISKCACHESTREAMINTERNAL
 
     nsresult    GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
     nsresult    GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
 
+    nsresult    CloseOutputStream(nsDiskCacheOutputStream * outputStream);
+    nsresult    CloseOutputStreamInternal(nsDiskCacheOutputStream * outputStream);
+        
+    nsresult    Write( const char * buffer,
+                       uint32_t     count,
+                       uint32_t *   bytesWritten);
+
     nsresult    Seek(int32_t whence, int32_t offset);
     nsresult    Tell(uint32_t * position);    
     nsresult    SetEOF();
 
     nsresult    ClearBinding();
     
     void        IncrementInputStreamCount() { PR_ATOMIC_INCREMENT(&mInStreamCount); }
     void        DecrementInputStreamCount()
@@ -47,32 +52,36 @@ public:
                 }
 
     size_t     SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf);
 
     // GCC 2.95.2 requires this to be defined, although we never call it.
     // and OS/2 requires that it not be private
     nsDiskCacheStreamIO() { NS_NOTREACHED("oops"); }
 private:
+
+
+    void        Close();
     nsresult    OpenCacheFile(int flags, PRFileDesc ** fd);
     nsresult    ReadCacheBlocks();
     nsresult    FlushBufferToFile();
     void        UpdateFileSize();
     void        DeleteBuffer();
+    nsresult    Flush();
 
     nsDiskCacheBinding *        mBinding;       // not an owning reference
     nsDiskCacheDevice *         mDevice;
+    nsDiskCacheOutputStream *   mOutStream;     // not an owning reference
     int32_t                     mInStreamCount;
     nsCOMPtr<nsIFile>           mLocalFile;
     PRFileDesc *                mFD;
 
     uint32_t                    mStreamPos;     // for Output Streams
     uint32_t                    mStreamEnd;
     uint32_t                    mBufPos;        // current mark in buffer
     uint32_t                    mBufEnd;        // current end of data in buffer
     uint32_t                    mBufSize;       // current end of buffer
-    bool                        mBufDirty;      // Where there is unflushed data in the buffer
-    bool                        mOutputStreamIsOpen; // Whether the output stream is open (for writing...)
+    bool                        mBufDirty;
     char *                      mBuffer;
     
 };
 
 #endif // _nsDiskCacheStreams_h_
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1630,16 +1630,28 @@
     "description": "Time spent (ms) during showdown deleting disk cache for 'clear private data' option"
   },
   "NETWORK_DISK_CACHE_REVALIDATION": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "description": "Total Time spent (ms) during disk cache revalidation"
   },
+  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": 10,
+    "description": "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)"
+  },
+  "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": 10,
+    "description": "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)"
+  },
   "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 10,
     "description": "Time spent in nsDiskCacheOutputStream::CloseInternal() on non-main thread (ms)"
   },
   "NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD": {
     "kind": "exponential",