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 idunknown
push userunknown
push dateunknown
reviewersmayhemer, blocking-betaN
bugs604897
milestone2.0b10pre
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.");