Bug 592520 - Do not fragment the hell out of CACHE__00[1-3]__. r=bsmedberg/jduell, a=betaN+
authorTaras Glek <tglek@mozilla.com>
Thu, 16 Sep 2010 13:21:12 -0700
changeset 54197 26e2971eeec9ac8b46f87b0e4431d631eeea1380
parent 54196 b1827f807513324b2e84866cdb0149c54c79641d
child 54198 1a7b01521bcd56e711e448a3af383b4c23f54f2e
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg, jduell, betaN
bugs592520
milestone2.0b7pre
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 592520 - Do not fragment the hell out of CACHE__00[1-3]__. r=bsmedberg/jduell, a=betaN+
configure.in
netwerk/cache/nsDiskCacheBlockFile.cpp
netwerk/cache/nsDiskCacheBlockFile.h
xpcom/glue/FileUtils.cpp
xpcom/glue/FileUtils.h
xpcom/glue/objs.mk
--- a/configure.in
+++ b/configure.in
@@ -8978,16 +8978,30 @@ if test "$USING_HCC"; then
    CC='${topsrcdir}/build/hcc'
    CC="$CC '$_OLDCC'"
    CXX='${topsrcdir}/build/hcpp'
    CXX="$CXX '$_OLDCXX'"
    AC_SUBST(CC)
    AC_SUBST(CXX)
 fi
 
+AC_MSG_CHECKING([for posix_fallocate])       
+AC_TRY_LINK([#define _XOPEN_SOURCE 600
+  #include <fcntl.h>],
+                 [posix_fallocate(0, 0, 0);],
+                 [ac_cv___posix_fallocate=true],
+                 [ac_cv___posix_fallocate=false])
+
+if test "$ac_cv___posix_fallocate" = true ; then
+  AC_DEFINE(HAVE_POSIX_FALLOCATE)
+  AC_MSG_RESULT(yes)
+else
+  AC_MSG_RESULT(no)
+fi
+
 dnl Check for missing components
 if test "$COMPILE_ENVIRONMENT"; then
 if test "$MOZ_X11"; then
     dnl ====================================================
     dnl = Check if X headers exist
     dnl ====================================================
     _SAVE_CFLAGS=$CFLAGS
     CFLAGS="$CFLAGS $XCFLAGS"
--- a/netwerk/cache/nsDiskCacheBlockFile.cpp
+++ b/netwerk/cache/nsDiskCacheBlockFile.cpp
@@ -35,60 +35,58 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #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)
 {
-    PRInt32   fileSize;
-
     mBlockSize = blockSize;
     
     // 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];
     if (!mBitMap) {
         rv = NS_ERROR_OUT_OF_MEMORY;
         goto error_exit;
     }
     
     // check if we just creating the file
-    fileSize = PR_Available(mFD);
-    if (fileSize < 0) {
+    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 (fileSize == 0) {
+    if (mFileSize == 0) {
         // initialize bit map and write it
         memset(mBitMap, 0, kBitMapBytes);
-        PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes);
-        if (bytesWritten < kBitMapBytes) 
+        if (!Write(0, mBitMap, kBitMapBytes))
             goto error_exit;
         
-    } else if (fileSize < kBitMapBytes) {
+    } else if (mFileSize < kBitMapBytes) {
         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) {
             rv = NS_ERROR_UNEXPECTED;
@@ -99,17 +97,17 @@ nsDiskCacheBlockFile::Open( nsILocalFile
         for (int i = 0; i < kBitMapWords; ++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)fileSize + blockSize < estimatedSize) {
+        if ((PRUint32)mFileSize + blockSize < estimatedSize) {
             rv = NS_ERROR_UNEXPECTED;
             goto error_exit;
         }
     }
     return NS_OK;
 
 error_exit:
     Close(PR_FALSE);
@@ -218,34 +216,26 @@ nsDiskCacheBlockFile::DeallocateBlocks( 
 nsresult
 nsDiskCacheBlockFile::WriteBlocks( void *   buffer,
                                    PRUint32 size,
                                    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);
     
     // seek to block position
     PRInt32 blockPos = kBitMapBytes + *startBlock * mBlockSize;
-    PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
-    NS_ENSURE_STATE(filePos == blockPos);
     
     // write the blocks
-    PRInt32 bytesWritten = PR_Write(mFD, buffer, size);
-    NS_ENSURE_STATE(bytesWritten >= 0 && PRUint32(bytesWritten) == size);
-    
-    // write the bit map and flush the file
-    // XXX except we would take a severe performance hit
-    // XXX rv = FlushBitMap();
-    return NS_OK;
+    return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 
 /******************************************************************************
  *  ReadBlocks
  *****************************************************************************/
 nsresult
 nsDiskCacheBlockFile::ReadBlocks( void *    buffer,
@@ -277,34 +267,30 @@ nsDiskCacheBlockFile::ReadBlocks( void *
 
 /******************************************************************************
  *  FlushBitMap
  *****************************************************************************/
 nsresult
 nsDiskCacheBlockFile::FlushBitMap()
 {
     if (!mBitMapDirty)  return NS_OK;
-
-    // seek to bitmap
-    PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET);
-    if (filePos != 0)  return NS_ERROR_UNEXPECTED;
     
 #if defined(IS_LITTLE_ENDIAN)
     PRUint32 bitmap[kBitMapWords];
     // Copy and swap to network format
     PRUint32 *p = bitmap;
     for (int i = 0; i < kBitMapWords; ++i, ++p)
       *p = htonl(mBitMap[i]);
 #else
     PRUint32 *bitmap = mBitMap;
 #endif
 
     // write bitmap
-    PRInt32 bytesWritten = PR_Write(mFD, bitmap, kBitMapBytes);
-    if (bytesWritten < kBitMapBytes)  return NS_ERROR_UNEXPECTED;
+    if (!Write(0, bitmap, kBitMapBytes))
+        return NS_ERROR_UNEXPECTED;
 
     PRStatus err = PR_Sync(mFD);
     if (err != PR_SUCCESS)  return NS_ERROR_UNEXPECTED;
 
     mBitMapDirty = PR_FALSE;
     return NS_OK;
 }
 
@@ -364,8 +350,43 @@ nsDiskCacheBlockFile::CalcBlockFileSize(
         if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; }
         if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; }
         if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; }
         estimatedSize +=  (i * 32 + lastBit + 1) * mBlockSize;
     }
 
     return estimatedSize;
 }
+
+/******************************************************************************
+ *  Write
+ *
+ *  Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
+ *
+ *****************************************************************************/
+bool
+nsDiskCacheBlockFile::Write(PRInt32 offset, const void *buf, PRInt32 amount)
+{
+    /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
+       20mb is a magic threshold because OSX stops autodefragging files bigger than that.
+       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) {
+        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;
+            mFileSize = PR_MIN(maxPreallocate, PR_MAX(mFileSize, minPreallocate));
+        }
+        mozilla::fallocate(mFD, mFileSize);
+    }
+    if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset)
+        return false;
+    return PR_Write(mFD, buf, amount) == amount;
+}
--- a/netwerk/cache/nsDiskCacheBlockFile.h
+++ b/netwerk/cache/nsDiskCacheBlockFile.h
@@ -75,19 +75,21 @@ public:
     nsresult  ReadBlocks( void * buffer, PRInt32  startBlock, PRInt32  numBlocks, 
                           PRInt32 * bytesRead);
     
 private:
     nsresult  FlushBitMap();
     PRInt32   AllocateBlocks( PRInt32  numBlocks);
     nsresult  VerifyAllocation( PRInt32 startBlock, PRInt32 numBLocks);
     PRUint32  CalcBlockFileSize();
+    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 *                  mBitMap;      // XXX future: array of bit map blocks
+    PRInt32                     mFileSize;
     PRBool                      mBitMapDirty;
 };
 
 #endif // _nsDiskCacheBlockFile_h_
new file mode 100644
--- /dev/null
+++ b/xpcom/glue/FileUtils.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Taras Glek <tglek@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if defined(XP_UNIX)
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#endif
+
+#include "nscore.h"
+#include "private/pprio.h"
+#include "mozilla/FileUtils.h"
+
+bool 
+mozilla::fallocate(PRFileDesc *aFD, PRInt64 aLength) 
+{
+#if defined(HAVE_POSIX_FALLOCATE)
+  return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0;
+#elif defined(XP_WIN)
+  return PR_Seek64(aFD, aLength, PR_SEEK_SET) == aLength
+    && 0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD));
+#elif defined(XP_MACOSX)
+  int fd = PR_FileDesc2NativeHandle(aFD);
+  fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength};
+  // Try to get a continous chunk of disk space
+  int ret = fcntl(fd, F_PREALLOCATE, &store);
+	if(-1 == ret){
+    // OK, perhaps we are too fragmented, allocate non-continuous
+    store.fst_flags = F_ALLOCATEALL;
+    ret = fcntl(fd, F_PREALLOCATE, &store);
+    if (-1 == ret)
+      return false;
+  }
+  return 0 == ftruncate(fd, aLength);
+#elif defined(XP_UNIX)
+  // The following is copied from fcntlSizeHint in sqlite
+  /* If the OS does not have posix_fallocate(), fake it. First use
+  ** ftruncate() to set the file size, then write a single byte to
+  ** the last byte in each block within the extended region. This
+  ** is the same technique used by glibc to implement posix_fallocate()
+  ** on systems that do not have a real fallocate() system call.
+  */
+  struct stat buf;
+  int fd = PR_FileDesc2NativeHandle(aFD);
+  if (fstat(fd, &buf))
+    return false;
+
+  if (buf.st_size >= aLength)
+    return false;
+
+  const int nBlk = buf.st_blksize;
+
+  if (!nBlk)
+    return false;
+
+  if (ftruncate(fd, aLength))
+    return false;
+  
+  int nWrite; // Return value from write()
+  PRInt64 iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk - 1; // Next offset to write to
+  do {
+    nWrite = 0;
+    if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite)
+      nWrite = PR_Write(aFD, "", 1);
+    iWrite += nBlk;
+  } while (nWrite == 1 && iWrite < aLength);
+  return nWrite == 1;
+#endif
+  return false;
+}
--- a/xpcom/glue/FileUtils.h
+++ b/xpcom/glue/FileUtils.h
@@ -36,16 +36,19 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_FileUtils_h
 #define mozilla_FileUtils_h
 namespace mozilla {
 
+/**
+ * AutoFDClose is a RAII wrapper for PRFileDesc.
+ **/
 class AutoFDClose
 {
 public:
   AutoFDClose(PRFileDesc* fd = nsnull) : mFD(fd) { }
   ~AutoFDClose() { if (mFD) PR_Close(mFD); }
 
   PRFileDesc* operator= (PRFileDesc *fd) {
     if (mFD) PR_Close(mFD);
@@ -55,10 +58,21 @@ public:
 
   operator PRFileDesc* () { return mFD; }
   PRFileDesc** operator &() { *this = nsnull; return &mFD; }
 
 private:
   PRFileDesc *mFD;
 };
 
+/**
+ * Fallocate efficiently and continuously allocates files via fallocate-type APIs.
+ * This is useful for avoiding fragmentation.
+ * On sucess the file be padded with zeros to grow to aLength.
+ *
+ * @param aFD file descriptor.
+ * @param aLength length of file to grow to.
+ * @return true on success.
+ */
+bool fallocate(PRFileDesc *aFD, PRInt64 aLength);
+
 } // namespace mozilla
 #endif
--- a/xpcom/glue/objs.mk
+++ b/xpcom/glue/objs.mk
@@ -76,11 +76,12 @@ XPCOM_GLUENS_SRC_LCPPSRCS =      \
   BlockingResourceBase.cpp       \
   DeadlockDetector.cpp           \
   SSE.cpp                        \
   unused.cpp                     \
   nsAutoLock.cpp                 \
   nsProxyRelease.cpp             \
   nsTextFormatter.cpp            \
   GenericFactory.cpp             \
+  FileUtils.cpp                  \
   $(NULL)
 
 XPCOM_GLUENS_SRC_CPPSRCS = $(addprefix $(topsrcdir)/xpcom/glue/,$(XPCOM_GLUENS_SRC_LCPPSRCS))