Bug 604897 - Unable to cache small entries when _CACHE_00[1-3]_ is full; r=mayhemer a=blocking-betaN+
authorMichal Novotny <michal.novotny@gmail.com>
Tue, 18 Jan 2011 16:12:10 +0200
changeset 60881 889cb1b4ee790c28168cd57108e8974bd5220f21
parent 60880 c729839a60342fd84db21877be5799845e49a25d
child 60882 fb0393b7957514eb46f07b588073ee944b8a0942
push id18150
push usereakhgari@mozilla.com
push dateWed, 19 Jan 2011 21:41:56 +0000
treeherdermozilla-central@889cb1b4ee79 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer, blocking-betaN
bugs604897
milestone2.0b10pre
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 604897 - Unable to cache small entries when _CACHE_00[1-3]_ is full; r=mayhemer a=blocking-betaN+
netwerk/cache/nsDiskCache.h
netwerk/cache/nsDiskCacheBlockFile.cpp
netwerk/cache/nsDiskCacheBlockFile.h
netwerk/cache/nsDiskCacheMap.cpp
netwerk/cache/nsDiskCacheMap.h
netwerk/cache/nsDiskCacheStreams.cpp
--- a/netwerk/cache/nsDiskCache.h
+++ b/netwerk/cache/nsDiskCache.h
@@ -48,17 +48,17 @@
 #ifdef XP_WIN
 #include <winsock.h>  // for htonl/ntohl
 #endif
 
 
 class nsDiskCache {
 public:
     enum {
-            kCurrentVersion = 0x00010012      // format = 16 bits major version/16 bits minor version
+            kCurrentVersion = 0x00010013      // format = 16 bits major version/16 bits minor version
     };
 
     enum { kData, kMetaData };
 
     // Parameter initval initializes internal state of hash function. Hash values are different
     // for the same text when different initval is used. It can be any random number.
     // 
     // It can be used for generating 64-bit hash value:
--- a/netwerk/cache/nsDiskCacheBlockFile.cpp
+++ b/netwerk/cache/nsDiskCacheBlockFile.cpp
@@ -41,65 +41,69 @@
 #include "nsDiskCache.h"
 #include "nsDiskCacheBlockFile.h"
 #include "mozilla/FileUtils.h"
 
 /******************************************************************************
  * nsDiskCacheBlockFile - 
  *****************************************************************************/
 
-const unsigned short kBitMapBytes = 4096;
-const unsigned short kBitMapWords = (kBitMapBytes/4);
-
 /******************************************************************************
  *  Open
  *****************************************************************************/
 nsresult
-nsDiskCacheBlockFile::Open( nsILocalFile *  blockFile, PRUint32  blockSize)
+nsDiskCacheBlockFile::Open(nsILocalFile * blockFile,
+                           PRUint32       blockSize,
+                           PRUint32       bitMapSize)
 {
+    if (bitMapSize % 32)
+        return NS_ERROR_INVALID_ARG;
+
     mBlockSize = blockSize;
+    mBitMapWords = bitMapSize / 32;
+    PRUint32 bitMapBytes = mBitMapWords * 4;
     
     // open the file - restricted to user, the data could be confidential
     nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
     if (NS_FAILED(rv))  return rv;  // unable to open or create file
     
     // allocate bit map buffer
-    mBitMap = new PRUint32[kBitMapWords];
+    mBitMap = new PRUint32[mBitMapWords];
     if (!mBitMap) {
         rv = NS_ERROR_OUT_OF_MEMORY;
         goto error_exit;
     }
     
     // check if we just creating the file
     mFileSize = PR_Available(mFD);
     if (mFileSize < 0) {
         // XXX an error occurred. We could call PR_GetError(), but how would that help?
         rv = NS_ERROR_UNEXPECTED;
         goto error_exit;
     }
     if (mFileSize == 0) {
         // initialize bit map and write it
-        memset(mBitMap, 0, kBitMapBytes);
-        if (!Write(0, mBitMap, kBitMapBytes))
+        memset(mBitMap, 0, bitMapBytes);
+        if (!Write(0, mBitMap, bitMapBytes))
             goto error_exit;
         
-    } else if (mFileSize < kBitMapBytes) {
+    } else if ((PRUint32)mFileSize < bitMapBytes) {
         rv = NS_ERROR_UNEXPECTED;  // XXX NS_ERROR_CACHE_INVALID;
         goto error_exit;
         
     } else {
         // read the bit map
-        const PRInt32 bytesRead = PR_Read(mFD, mBitMap, kBitMapBytes);
-        if (bytesRead < kBitMapBytes) {
+        const PRInt32 bytesRead = PR_Read(mFD, mBitMap, bitMapBytes);
+        if ((bytesRead < 0) || ((PRUint32)bytesRead < bitMapBytes)) {
             rv = NS_ERROR_UNEXPECTED;
             goto error_exit;
         }
 #if defined(IS_LITTLE_ENDIAN)
         // Swap from network format
-        for (int i = 0; i < kBitMapWords; ++i)
+        for (unsigned int i = 0; i < mBitMapWords; ++i)
             mBitMap[i] = ntohl(mBitMap[i]);
 #endif
         // validate block file size
         // Because not whole blocks are written, the size may be a 
         // little bit smaller than used blocks times blocksize,
         // because the last block will generally not be 'whole'.
         const PRUint32  estimatedSize = CalcBlockFileSize();
         if ((PRUint32)mFileSize + blockSize < estimatedSize) {
@@ -150,33 +154,33 @@ nsDiskCacheBlockFile::Close(PRBool flush
  *  Returns block number of first block allocated or -1 on failure.
  *
  *****************************************************************************/
 PRInt32
 nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks)
 {
     const int maxPos = 32 - numBlocks;
     const PRUint32 mask = (0x01 << numBlocks) - 1;
-    for (int i = 0; i < kBitMapWords; ++i) {
+    for (unsigned int i = 0; i < mBitMapWords; ++i) {
         PRUint32 mapWord = ~mBitMap[i]; // flip bits so free bits are 1
         if (mapWord) {                  // At least one free bit
             // Binary search for first free bit in word
             int bit = 0;
             if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; }
             if ((mapWord & 0x000FF) == 0) { bit |= 8;  mapWord >>= 8;  }
             if ((mapWord & 0x0000F) == 0) { bit |= 4;  mapWord >>= 4;  }
             if ((mapWord & 0x00003) == 0) { bit |= 2;  mapWord >>= 2;  }
             if ((mapWord & 0x00001) == 0) { bit |= 1;  mapWord >>= 1;  }
             // Find first fit for mask
             for (; bit <= maxPos; ++bit) {
                 // all bits selected by mask are 1, so free
                 if ((mask & mapWord) == mask) {
                     mBitMap[i] |= mask << bit; 
                     mBitMapDirty = PR_TRUE;
-                    return i * 32 + bit;
+                    return (PRInt32)i * 32 + bit;
                 }
             }
         }
     }
     
     return -1;
 }
 
@@ -184,17 +188,17 @@ nsDiskCacheBlockFile::AllocateBlocks(PRI
 /******************************************************************************
  *  DeallocateBlocks
  *****************************************************************************/
 nsresult
 nsDiskCacheBlockFile::DeallocateBlocks( PRInt32  startBlock, PRInt32  numBlocks)
 {
     if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
 
-    if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) ||
+    if ((startBlock < 0) || ((PRUint32)startBlock > mBitMapWords * 32 - 1) ||
         (numBlocks < 1)  || (numBlocks > 4))
        return NS_ERROR_ILLEGAL_VALUE;
            
     const PRInt32 startWord = startBlock >> 5;      // Divide by 32
     const PRUint32 startBit = startBlock & 31;      // Modulo by 32 
       
     // make sure requested deallocation doesn't span a word boundary
     if (startBit + numBlocks > 32)  return NS_ERROR_UNEXPECTED;
@@ -219,20 +223,21 @@ nsDiskCacheBlockFile::WriteBlocks( void 
                                    PRInt32  numBlocks,
                                    PRInt32 * startBlock)
 {
     // presume buffer != nsnull and startBlock != nsnull
     NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE);
 
     // allocate some blocks in the cache block file
     *startBlock = AllocateBlocks(numBlocks);
-    NS_ENSURE_STATE(*startBlock >= 0);
-    
+    if (*startBlock < 0)
+        return NS_ERROR_NOT_AVAILABLE;
+
     // seek to block position
-    PRInt32 blockPos = kBitMapBytes + *startBlock * mBlockSize;
+    PRInt32 blockPos = mBitMapWords * 4 + *startBlock * mBlockSize;
     
     // write the blocks
     return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 
 /******************************************************************************
  *  ReadBlocks
@@ -245,17 +250,17 @@ nsDiskCacheBlockFile::ReadBlocks( void *
 {
     // presume buffer != nsnull and bytesRead != bytesRead
 
     if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
     nsresult rv = VerifyAllocation(startBlock, numBlocks);
     if (NS_FAILED(rv))  return rv;
     
     // seek to block position
-    PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize;
+    PRInt32 blockPos = mBitMapWords * 4 + startBlock * mBlockSize;
     PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
     if (filePos != blockPos)  return NS_ERROR_UNEXPECTED;
 
     // read the blocks
     PRInt32 bytesToRead = *bytesRead;
     if ((bytesToRead <= 0) || ((PRUint32)bytesToRead > mBlockSize * numBlocks)) {
         bytesToRead = mBlockSize * numBlocks;
     }
@@ -269,27 +274,31 @@ nsDiskCacheBlockFile::ReadBlocks( void *
  *  FlushBitMap
  *****************************************************************************/
 nsresult
 nsDiskCacheBlockFile::FlushBitMap()
 {
     if (!mBitMapDirty)  return NS_OK;
     
 #if defined(IS_LITTLE_ENDIAN)
-    PRUint32 bitmap[kBitMapWords];
+    PRUint32 *bitmap = new PRUint32[mBitMapWords];
     // Copy and swap to network format
     PRUint32 *p = bitmap;
-    for (int i = 0; i < kBitMapWords; ++i, ++p)
+    for (unsigned int i = 0; i < mBitMapWords; ++i, ++p)
       *p = htonl(mBitMap[i]);
 #else
     PRUint32 *bitmap = mBitMap;
 #endif
 
     // write bitmap
-    if (!Write(0, bitmap, kBitMapBytes))
+    bool written = Write(0, bitmap, mBitMapWords * 4);
+#if defined(IS_LITTLE_ENDIAN)
+    delete [] bitmap;
+#endif
+    if (!written)
         return NS_ERROR_UNEXPECTED;
 
     PRStatus err = PR_Sync(mFD);
     if (err != PR_SUCCESS)  return NS_ERROR_UNEXPECTED;
 
     mBitMapDirty = PR_FALSE;
     return NS_OK;
 }
@@ -302,17 +311,17 @@ nsDiskCacheBlockFile::FlushBitMap()
  *      NS_OK if all bits are marked allocated
  *      NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
  *      NS_ERROR_FAILURE if some or all the bits are marked unallocated
  *
  *****************************************************************************/
 nsresult
 nsDiskCacheBlockFile::VerifyAllocation( PRInt32  startBlock, PRInt32  numBlocks)
 {
-    if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) ||
+    if ((startBlock < 0) || ((PRUint32)startBlock > mBitMapWords * 32 - 1) ||
         (numBlocks < 1)  || (numBlocks > 4))
        return NS_ERROR_ILLEGAL_VALUE;
     
     const PRInt32 startWord = startBlock >> 5;      // Divide by 32
     const PRUint32 startBit = startBlock & 31;      // Modulo by 32 
       
     // make sure requested deallocation doesn't span a word boundary
     if (startBit + numBlocks > 32)  return NS_ERROR_ILLEGAL_VALUE;
@@ -330,18 +339,18 @@ nsDiskCacheBlockFile::VerifyAllocation( 
  *
  *  Return size of the block file according to the bits set in mBitmap
  *
  *****************************************************************************/
 PRUint32
 nsDiskCacheBlockFile::CalcBlockFileSize()
 {
     // search for last byte in mBitMap with allocated bits
-    PRUint32  estimatedSize = kBitMapBytes;      
-    PRInt32   i = kBitMapWords;
+    PRUint32  estimatedSize = mBitMapWords * 4;
+    PRInt32   i = mBitMapWords;
     while (--i >= 0) {
         if (mBitMap[i]) break;
     }
 
     if (i >= 0) {
         // binary search to find last allocated bit in byte
         PRUint32 mapWord = mBitMap[i];
         PRUint32 lastBit = 31;
@@ -370,17 +379,17 @@ nsDiskCacheBlockFile::Write(PRInt32 offs
        Beyond 20mb grow in 4mb chunks.
      */
     const PRInt32 upTo = offset + amount;
     // Use a conservative definition of 20MB
     const PRInt32 minPreallocate = 4*1024*1024;
     const PRInt32 maxPreallocate = 20*1000*1000;
     if (mFileSize < upTo) {
         // maximal file size
-        const PRInt32 maxFileSize = kBitMapBytes * (mBlockSize * 8 + 1);
+        const PRInt32 maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1);
         if (upTo > maxPreallocate) {
             // grow the file as a multiple of minPreallocate
             mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate;
         } else {
             // Grow quickly between 1MB to 20MB
             if (mFileSize)
                 while(mFileSize < upTo)
                     mFileSize *= 2;
--- a/netwerk/cache/nsDiskCacheBlockFile.h
+++ b/netwerk/cache/nsDiskCacheBlockFile.h
@@ -52,22 +52,24 @@
  *
  *****************************************************************************/
 class nsDiskCacheBlockFile {
 public:
     nsDiskCacheBlockFile()
            : mFD(nsnull)
            , mBitMap(nsnull)
            , mBlockSize(0)
+           , mBitMapWords(0)
            , mFileSize(0)
            , mBitMapDirty(PR_FALSE)
             {}
     ~nsDiskCacheBlockFile() { (void) Close(PR_TRUE); }
     
-    nsresult  Open( nsILocalFile *  blockFile, PRUint32  blockSize);
+    nsresult  Open( nsILocalFile *  blockFile, PRUint32  blockSize,
+                    PRUint32  bitMapSize);
     nsresult  Close(PRBool flush);
     
     /*
      * Trim
      * Truncates the block file to the end of the last allocated block.
      */
     nsresult  Trim() { return nsDiskCache::Truncate(mFD, CalcBlockFileSize()); }
     nsresult  DeallocateBlocks( PRInt32  startBlock, PRInt32  numBlocks);
@@ -84,13 +86,14 @@ private:
     bool   Write(PRInt32 offset, const void *buf, PRInt32 amount);
 
 /**
  *  Data members
  */
     PRFileDesc *                mFD;
     PRUint32 *                  mBitMap;      // XXX future: array of bit map blocks
     PRUint32                    mBlockSize;
+    PRUint32                    mBitMapWords;
     PRInt32                     mFileSize;
     PRBool                      mBitMapDirty;
 };
 
 #endif // _nsDiskCacheBlockFile_h_
--- a/netwerk/cache/nsDiskCacheMap.cpp
+++ b/netwerk/cache/nsDiskCacheMap.cpp
@@ -202,17 +202,17 @@ nsDiskCacheMap::Close(PRBool flush)
     return rv;
 }
 
 
 nsresult
 nsDiskCacheMap::Trim()
 {
     nsresult rv, rv2 = NS_OK;
-    for (int i=0; i < 3; ++i) {
+    for (int i=0; i < kNumBlockFiles; ++i) {
         rv = mBlockFile[i].Trim();
         if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
     }
     // Try to shrink the records array
     rv = ShrinkRecords();
     if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
     return rv2;
 }
@@ -608,51 +608,52 @@ nsDiskCacheMap::EvictRecords( nsDiskCach
 
 nsresult
 nsDiskCacheMap::OpenBlockFiles()
 {
     // create nsILocalFile for block file
     nsCOMPtr<nsILocalFile> blockFile;
     nsresult rv = NS_OK;
     
-    for (int i = 0; i < 3; ++i) {
+    for (int i = 0; i < kNumBlockFiles; ++i) {
         rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
         if (NS_FAILED(rv)) break;
     
         PRUint32 blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
-        rv = mBlockFile[i].Open(blockFile, blockSize);
+        PRUint32 bitMapSize = GetBitMapSizeForIndex(i+1);
+        rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize);
         if (NS_FAILED(rv)) break;
     }
     // close all files in case of any error
     if (NS_FAILED(rv)) 
         (void)CloseBlockFiles(PR_FALSE); // we already have an error to report
 
     return rv;
 }
 
 
 nsresult
 nsDiskCacheMap::CloseBlockFiles(PRBool flush)
 {
     nsresult rv, rv2 = NS_OK;
-    for (int i=0; i < 3; ++i) {
+    for (int i=0; i < kNumBlockFiles; ++i) {
         rv = mBlockFile[i].Close(flush);
         if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
     }
     return rv2;
 }
 
 
 PRBool
 nsDiskCacheMap::CacheFilesExist()
 {
     nsCOMPtr<nsILocalFile> blockFile;
     nsresult rv;
     
-    for (int i = 0; i < 3; ++i) {
+    for (int i = 0; i < kNumBlockFiles; ++i) {
         PRBool exists;
         rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
         if (NS_FAILED(rv))  return PR_FALSE;
 
         rv = blockFile->Exists(&exists);
         if (NS_FAILED(rv) || !exists)  return PR_FALSE;
     }
 
@@ -723,17 +724,17 @@ nsDiskCacheMap::ReadDiskCacheEntry(nsDis
                 if (bytesRead < fileSize) {
                     rv = NS_ERROR_UNEXPECTED;
                 }
             }
         }
         PR_Close(fd);
         NS_ENSURE_SUCCESS(rv, nsnull);
 
-    } else if (metaFile < 4) {  // XXX magic number: use constant
+    } else if (metaFile < (kNumBlockFiles + 1)) {
         // entry/metadata stored in cache block file
         
         // allocate buffer
         PRUint32 blockCount = record->MetaBlockCount();
         bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
 
         rv = EnsureBuffer(bytesRead);
         NS_ENSURE_SUCCESS(rv, nsnull);
@@ -834,17 +835,50 @@ nsDiskCacheMap::WriteDiskCacheEntry(nsDi
                          "generations out of sync");
         } else {
             rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
-        
+    // write entry data to disk cache block file
+    diskEntry->Swap();
+
+    if (fileIndex != 0) {
+        while (1) {
+            PRUint32  blockSize = GetBlockSizeForIndex(fileIndex);
+            PRUint32  blocks    = ((size - 1) / blockSize) + 1;
+
+            PRInt32 startBlock;
+            rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
+                                                       &startBlock);
+            if (NS_SUCCEEDED(rv)) {
+                // update binding and cache map record
+                binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
+
+                rv = UpdateRecord(&binding->mRecord);
+                NS_ENSURE_SUCCESS(rv, rv);
+
+                // XXX we should probably write out bucket ourselves
+
+                IncrementTotalSize(blocks, blockSize);
+                break;
+            }
+
+            if (fileIndex == kNumBlockFiles) {
+                fileIndex = 0; // write data to separate file
+                break;
+            }
+
+            // try next block file
+            fileIndex++;
+        }
+    }
+
     if (fileIndex == 0) {
         // Write entry data to separate file
         PRUint32 metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
         nsCOMPtr<nsILocalFile> localFile;
         
         // XXX handle metaFileSizeK > USHRT_MAX
         binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
         binding->mRecord.SetMetaFileSize(metaFileSizeK);
@@ -859,47 +893,24 @@ nsDiskCacheMap::WriteDiskCacheEntry(nsDi
         
         // open the file
         PRFileDesc * fd;
         // open the file - restricted to user, the data could be confidential
         rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
         NS_ENSURE_SUCCESS(rv, rv);
 
         // write the file
-        diskEntry->Swap();
         PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
         
         PRStatus err = PR_Close(fd);
         if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
             return NS_ERROR_UNEXPECTED;
         }
         // XXX handle metaFileSizeK == USHRT_MAX
         IncrementTotalSize(metaFileSizeK);
-        
-    } else {
-        PRUint32  blockSize = GetBlockSizeForIndex(fileIndex);
-        PRUint32  blocks    = ((size - 1) / blockSize) + 1;
-
-        // write entry data to disk cache block file
-        diskEntry->Swap();
-        PRInt32 startBlock;
-        nsresult rv2 = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks, &startBlock);
-        // Call UpdateRecord() even if WriteBlocks() has failed. We need to
-        // update the record because we've changed the eviction rank.
-        if (NS_SUCCEEDED(rv2)) {
-            // update binding and cache map record
-            binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
-        }
-        rv = UpdateRecord(&binding->mRecord);
-        NS_ENSURE_SUCCESS(rv, rv);
-        NS_ENSURE_SUCCESS(rv2, rv2);
-
-        // XXX we should probably write out bucket ourselves
-        
-        IncrementTotalSize(blocks, blockSize);
     }
 
     return rv;
 }
 
 
 nsresult
 nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
@@ -927,29 +938,38 @@ nsDiskCacheMap::WriteDataCacheBlocks(nsD
 {
     CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
         binding->mRecord.HashNumber(), size));
 
     nsresult  rv = NS_OK;
     
     // determine block file & number of blocks
     PRUint32  fileIndex  = CalculateFileIndex(size);
-    PRUint32  blockSize  = GetBlockSizeForIndex(fileIndex);
     PRUint32  blockCount = 0;
     PRInt32   startBlock = 0;
-    
+
     if (size > 0) {
-        blockCount = ((size - 1) / blockSize) + 1;
+        while (1) {
+            PRUint32  blockSize  = GetBlockSizeForIndex(fileIndex);
+            blockCount = ((size - 1) / blockSize) + 1;
 
-        rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount, &startBlock);
-        NS_ENSURE_SUCCESS(rv, rv);
-        
-        IncrementTotalSize(blockCount, blockSize);
+            rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
+                                                       &startBlock);
+            if (NS_SUCCEEDED(rv)) {
+                IncrementTotalSize(blockCount, blockSize);
+                break;
+            }
+
+            if (fileIndex == kNumBlockFiles)
+                return rv;
+
+            fileIndex++;
+        }
     }
-    
+
     // update binding and cache map record
     binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
     if (!binding->mDoomed) {
         rv = UpdateRecord(&binding->mRecord);
     }
     return rv;
 }
 
@@ -979,17 +999,17 @@ nsDiskCacheMap::DeleteStorage(nsDiskCach
         // XXX if sizeK == USHRT_MAX, stat file for actual size
 
         rv = GetFileForDiskCacheRecord(record, metaData, PR_FALSE, getter_AddRefs(file));
         if (NS_SUCCEEDED(rv)) {
             rv = file->Remove(PR_FALSE);    // false == non-recursive
         }
         DecrementTotalSize(sizeK);
         
-    } else if (fileIndex < 4) {
+    } else if (fileIndex < (kNumBlockFiles + 1)) {
         // deallocate blocks
         PRUint32  startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
         PRUint32  blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
         
         rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
         DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
     }
     if (metaData)  record->ClearMetaLocation();
@@ -1080,20 +1100,24 @@ nsDiskCacheMap::GetBlockFileForIndex(PRU
 
     return rv;
 }
 
 
 PRUint32
 nsDiskCacheMap::CalculateFileIndex(PRUint32 size)
 {
-    if (size <=  1024)  return 1;
-    if (size <=  4096)  return 2;
-    if (size <= 16384)  return 3;
-    return 0;  
+    // We prefer to use block file with larger block if the wasted space would
+    // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
+    // instead of in 4 1K-blocks.
+
+    if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1))  return 1;
+    if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2))  return 2;
+    if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3))  return 3;
+    return 0;
 }
 
 nsresult
 nsDiskCacheMap::EnsureBuffer(PRUint32 bufSize)
 {
     if (mBufferSize < bufSize) {
         char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
         if (!buf) {
--- a/netwerk/cache/nsDiskCacheMap.h
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -79,17 +79,26 @@ struct nsDiskCacheEntry;
  *      3 = 4k block file
  *
  *  eFileSizeMask note:  Files larger than 64 MiB have zero size stored in the
  *                       location.  The file itself must be examined to determine
  *                       its actual size.  (XXX This is broken in places -darin)
  *
  *****************************************************************************/
 
-#define BLOCK_SIZE_FOR_INDEX(index)  ((index) ? (256 << (2 * ((index) - 1))) : 0)
+/*
+  We have 3 block files with roughly the same max size (32MB)
+    1 - block size 256B, number of blocks 131072
+    2 - block size  1kB, number of blocks  32768
+    3 - block size  4kB, number of blocks   8192
+*/
+#define kNumBlockFiles             3
+#define SIZE_SHIFT(idx)            (2 * ((idx) - 1))
+#define BLOCK_SIZE_FOR_INDEX(idx)  ((idx) ? (256    << SIZE_SHIFT(idx)) : 0)
+#define BITMAP_SIZE_FOR_INDEX(idx) ((idx) ? (131072 >> SIZE_SHIFT(idx)) : 0)
 
 // Min and max values for the number of records in the DiskCachemap
 #define kMinRecordCount    512
 
 #define kSeparateFile      0
 // #define must always  be <= 65535KB, or overflow. See bug 443067 Comment 8
 #define kMaxDataFileSize   5 * 1024 * 1024  // 5 MB (in bytes) 
 #define kBuckets           (1 << 5)    // must be a power of 2!
@@ -156,17 +165,17 @@ public:
     }
 
     void      SetDataBlocks( PRUint32 index, PRUint32 startBlock, PRUint32 blockCount)
     {
         // clear everything
         mDataLocation = 0;
         
         // set file index
-        NS_ASSERTION( index < 4,"invalid location index");
+        NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
         NS_ASSERTION( index > 0,"invalid location index");
         mDataLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
 
         // set startBlock
         NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
         mDataLocation |= startBlock & eBlockNumberMask;
         
         // set blockCount
@@ -224,17 +233,17 @@ public:
     }
 
     void      SetMetaBlocks( PRUint32 index, PRUint32 startBlock, PRUint32 blockCount)
     {
         // clear everything
         mMetaLocation = 0;
         
         // set file index
-        NS_ASSERTION( index < 4, "invalid location index");
+        NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
         NS_ASSERTION( index > 0, "invalid location index");
         mMetaLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
 
         // set startBlock
         NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
         mMetaLocation |= startBlock & eBlockNumberMask;
         
         // set blockCount
@@ -504,16 +513,19 @@ private:
     nsresult    CreateCacheSubDirectories();
 
     PRUint32    CalculateFileIndex(PRUint32 size);
 
     nsresult    GetBlockFileForIndex( PRUint32 index, nsILocalFile ** result);
     PRUint32    GetBlockSizeForIndex( PRUint32 index) const {
         return BLOCK_SIZE_FOR_INDEX(index);
     }
+    PRUint32    GetBitMapSizeForIndex( PRUint32 index) const {
+        return BITMAP_SIZE_FOR_INDEX(index);
+    }
     
     // returns the bucket number    
     PRUint32 GetBucketIndex( PRUint32 hashNumber) const {
         return (hashNumber & (kBuckets - 1));
     }
     
     // Gets the size of the bucket (in number of records)
     PRUint32 GetRecordsPerBucket() const {
@@ -543,16 +555,16 @@ private:
 
 /**
  *  data members
  */
 private:
     nsCOMPtr<nsILocalFile>  mCacheDirectory;
     PRFileDesc *            mMapFD;
     nsDiskCacheRecord *     mRecordArray;
-    nsDiskCacheBlockFile    mBlockFile[3];
+    nsDiskCacheBlockFile    mBlockFile[kNumBlockFiles];
     PRUint32                mBufferSize;
     char *                  mBuffer;
     nsDiskCacheHeader       mHeader;
     PRInt32                 mMaxRecordCount;
 };
 
 #endif // _nsDiskCacheMap_h_
--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -497,21 +497,50 @@ nsDiskCacheStreamIO::Flush()
         mBinding->mRecord.HashNumber(), mBinding->mDoomed));
 
     if (!mBufDirty)
         return NS_OK;
 
     // write data to cache blocks, or flush mBuffer to file
     nsDiskCacheMap *cacheMap = mDevice->CacheMap();  // get map reference
     nsresult rv;
-    
-    if ((mStreamEnd > kMaxBufferSize) ||
-        (mBinding->mCacheEntry->StoragePolicy() == nsICache::STORE_ON_DISK_AS_FILE)) {
+
+    PRBool written = PR_FALSE;
+
+    if ((mStreamEnd <= kMaxBufferSize) &&
+        (mBinding->mCacheEntry->StoragePolicy() != nsICache::STORE_ON_DISK_AS_FILE)) {
+        // store data (if any) in cache block files
+
+        mBufDirty = PR_FALSE;
+
+        // delete existing storage
+        nsDiskCacheRecord * record = &mBinding->mRecord;
+        if (record->DataLocationInitialized()) {
+            rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
+            if (NS_FAILED(rv)) {
+                NS_WARNING("cacheMap->DeleteStorage() failed.");
+                cacheMap->DeleteRecord(record);
+                return rv;
+            }
+        }
+
+        // flush buffer to block files
+        written = PR_TRUE;
+        if (mStreamEnd > 0) {
+            rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mBufEnd);
+            if (NS_FAILED(rv)) {
+                NS_WARNING("WriteDataCacheBlocks() failed.");
+                written = PR_FALSE;
+            }
+        }
+    }
+
+    if (!written) {
         // make sure we save as separate file
-        rv = FlushBufferToFile();       // will initialize DataFileLocation() if necessary
+        rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
 
         if (mFD) {
           // Update the file size of the disk file in the cache
           UpdateFileSize();
 
           // close file descriptor
           (void) PR_Close(mFD);
           mFD = nsnull;
@@ -524,42 +553,16 @@ nsDiskCacheStreamIO::Flush()
         NS_ENSURE_SUCCESS(rv, rv);
 
         // since the data location is on disk as a single file, the only value
         // in keeping mBuffer around is to avoid an extra malloc the next time
         // we need to write to this file.  reading will use a file descriptor.
         // therefore, it's probably not worth optimizing for the subsequent
         // write, so we unconditionally delete mBuffer here.
         DeleteBuffer();
-
-    } else {
-        // store data (if any) in cache block files
-
-        mBufDirty = PR_FALSE;
-
-        // delete existing storage
-        nsDiskCacheRecord * record = &mBinding->mRecord;
-        if (record->DataLocationInitialized()) {
-            rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
-            if (NS_FAILED(rv)) {
-                NS_WARNING("cacheMap->DeleteStorage() failed.");
-                cacheMap->DeleteRecord(record);
-                return  rv;
-            }
-        }
-    
-        // flush buffer to block files
-        if (mStreamEnd > 0) {
-            rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mBufEnd);
-            if (NS_FAILED(rv)) {
-                NS_WARNING("WriteDataCacheBlocks() failed.");
-                nsCacheService::DoomEntry(mBinding->mCacheEntry);
-                return rv;
-            }
-        }
     }
     
     // XXX do we need this here?  WriteDataCacheBlocks() calls UpdateRecord()
     // update cache map if entry isn't doomed
     if (!mBinding->mDoomed) {
         rv = cacheMap->UpdateRecord(&mBinding->mRecord);
         if (NS_FAILED(rv)) {
             NS_WARNING("cacheMap->UpdateRecord() failed.");