add support for closing inactive databases, r=neil, sr=standard8, bug 723248
authorDavid Bienvenu <bienvenu@nventure.com>
Thu, 26 Apr 2012 14:19:05 -0700
changeset 10024 83fd926bf1f8f5908fed1dff064221086d361566
parent 10023 aa2610f9224fb974a5d0d861a15c6245692f4903
child 10025 982b3dad05ea56e0fbaff958292f82f9f982c4d0
push id7629
push userbienvenu@nventure.com
push dateThu, 26 Apr 2012 21:18:51 +0000
treeherdercomm-central@83fd926bf1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, standard8, bug
bugs723248
add support for closing inactive databases, r=neil, sr=standard8, bug 723248
mail/base/content/msgMail3PaneWindow.js
mail/base/modules/Makefile.in
mail/base/modules/msgDBCacheManager.js
mailnews/base/util/nsMsgDBFolder.cpp
mailnews/db/msgdb/public/nsIMsgDatabase.idl
mailnews/db/msgdb/public/nsMsgDatabase.h
mailnews/db/msgdb/src/nsMsgDatabase.cpp
mailnews/local/src/nsLocalMailFolder.cpp
mailnews/mailnews.js
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -48,16 +48,17 @@ Components.utils.import("resource:///mod
 Components.utils.import("resource:///modules/jsTreeSelection.js");
 Components.utils.import("resource:///modules/MailConsts.js");
 Components.utils.import("resource:///modules/errUtils.js");
 Components.utils.import("resource:///modules/IOUtils.js");
 Components.utils.import("resource:///modules/mailnewsMigrator.js");
 Components.utils.import("resource:///modules/sessionStoreManager.js");
 Components.utils.import("resource:///modules/summaryFrameManager.js");
 Components.utils.import("resource:///modules/mailInstrumentation.js");
+Components.utils.import("resource:///modules/msgDBCacheManager.js");
 
 /* This is where functions related to the 3 pane window are kept */
 
 // from MailNewsTypes.h
 const nsMsgKey_None = 0xFFFFFFFF;
 const nsMsgViewIndex_None = 0xFFFFFFFF;
 const kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
 
@@ -377,16 +378,17 @@ function OnLoadMessenger()
 
   gPrefBranch.addObserver("mail.pane_config.dynamic", MailPrefObserver, false);
   gPrefBranch.addObserver("mail.showCondensedAddresses", MailPrefObserver,
                           false);
 
   MailOfflineMgr.init();
   CreateMailWindowGlobals();
   GetMessagePaneWrapper().collapsed = true;
+  msgDBCacheManager.init();
 
   // This needs to be before we throw up the account wizard on first run.
   try {
     mailInstrumentationManager.init();
   } catch(ex) {logException(ex);}
 
   // - initialize tabmail system
   // Do this before LoadPostAccountWizard since that code selects the first
--- a/mail/base/modules/Makefile.in
+++ b/mail/base/modules/Makefile.in
@@ -48,16 +48,17 @@ EXTRA_JS_MODULES = \
   MailUtils.js \
   attachmentChecker.js \
   dbViewWrapper.js \
   mailViewManager.js \
   quickFilterManager.js \
   searchSpec.js \
   MsgHdrSyntheticView.js \
   sessionStoreManager.js \
+  msgDBCacheManager.js \
   summaryFrameManager.js \
   mailMigrator.js \
   mailInstrumentation.js \
   glodaWebSearch.js \
   oauth.jsm \
   http.jsm \
   distribution.js \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/mail/base/modules/msgDBCacheManager.js
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Message DB Cache manager
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const EXPORTED_SYMBOLS = ["msgDBCacheManager"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/IOUtils.js");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ */
+const DBCACHE_INTERVAL_DEFAULT_MS = 60000; // 1 minute
+
+/* :::::::: The Module ::::::::::::::: */
+
+var msgDBCacheManager =
+{
+  _initialized: false,
+
+  _msgDBCacheTimer: null,
+
+  _msgDBCacheTimerIntervalMS: DBCACHE_INTERVAL_DEFAULT_MS,
+
+  /**
+   * This is called on startup
+   */
+  init: function dbcachemgr_init()
+  {
+
+    // we listen for "quit-application-granted" instead of
+    // "quit-application-requested" because other observers of the
+    // latter can cancel the shutdown.
+    var observerSvc = Cc["@mozilla.org/observer-service;1"]
+                      .getService(Ci.nsIObserverService);
+    observerSvc.addObserver(this, "quit-application-granted", false);
+
+    this.startPeriodicCheck();
+
+    this._initialized = true;
+  },
+
+/* ........ Timer Callback ................*/
+
+  _dbCacheCheckTimerCallback: function dbCache_CheckTimerCallback()
+  {
+    msgDBCacheManager.checkCachedDBs();
+  },
+
+/* ........ Observer Notification Handler ................*/
+
+  observe: function dbCache_observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+    // This is observed before any windows start unloading if something other
+    // than the last 3pane window closing requested the application be
+    // shutdown. For example, when the user quits via the file menu.
+    case "quit-application-granted":
+      this.stopPeriodicCheck();
+      break;
+    }
+  },
+
+/* ........ Public API ................*/
+
+  /**
+   * Stops db cache check
+   */
+  stopPeriodicCheck: function dbcache_stopPeriodicCheck()
+  {
+    if (this._dbCacheCheckTimer) {
+      this._dbCacheCheckTimer.cancel();
+
+      delete this._dbCacheCheckTimer;
+      this._dbCacheCheckTimer = null;
+    }
+  },
+
+  /**
+   * Starts periodic db cache check
+   */
+  startPeriodicCheck: function dbcache_startPeriodicCheck()
+  {
+    if (!this._dbCacheCheckTimer) {
+      this._dbCacheCheckTimer = Cc["@mozilla.org/timer;1"]
+                                   .createInstance(Ci.nsITimer);
+
+      this._dbCacheCheckTimer.initWithCallback(
+                                   this._dbCacheCheckTimerCallback,
+                                   this._msgDBCacheTimerIntervalMS,
+                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
+    }
+  },
+  checkCachedDBs : function ()
+  {
+    const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"]
+                         .getService(Ci.nsIMsgDBService);
+    const mailSession = Cc["@mozilla.org/messenger/services/session;1"]
+                          .getService(Ci.nsIMsgMailSession);
+
+    let idleLimit = Services.prefs.getIntPref("mail.db.idle_limit");
+    let maxOpenDBs = Services.prefs.getIntPref("mail.db.max_open");
+
+    let closeThreshold = Date.now() - idleLimit;
+    const nsMsgFolderFlags = Ci.nsMsgFolderFlags;
+    let cachedDBs = gDbService.openDBs;
+    let numOpenDBs = 0;
+    for (let i = 0; i < cachedDBs.length; i++) {
+      db = cachedDBs.queryElementAt(i, Ci.nsIMsgDatabase);
+      if (mailSession.IsFolderOpenInWindow(db.folder)) {
+        numOpenDBs++;
+        continue;
+      }
+      let lruTime = db.lastUseTime / 1000;
+      if (lruTime < closeThreshold)
+        db.folder.msgDatabase = null;
+      numOpenDBs++;
+    }
+    let openDBs = gDbService.openDBs;
+    if (numOpenDBs > maxOpenDBs) {
+      let dbs = [];
+      for (let i = 0; i < openDBs.length; i++)
+        dbs.push(openDBs.queryElementAt(i, Ci.nsIMsgDatabase));
+      function sortByLastUse(a, b) {
+        return a.lastUseTime > b.lastUseTime;
+      }
+      dbs.sort(sortByLastUse);
+      let dbsToClose = maxOpenDBs - dbs.length;
+      for each (let [, db] in Iterator(dbs)) {
+        if (mailSession.IsFolderOpenInWindow(db.folder))
+          continue;
+        db.folder.msgDatabase = null;
+        if (--dbsToClose == 0)
+          break;
+      }
+    }
+  },
+};
--- a/mailnews/base/util/nsMsgDBFolder.cpp
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -949,16 +949,17 @@ nsresult nsMsgDBFolder::CreateFileForDB(
 NS_IMETHODIMP
 nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase)
 {
   NS_ENSURE_ARG_POINTER(aMsgDatabase);
   GetDatabase();
   if (!mDatabase)
     return NS_ERROR_FAILURE;
   NS_ADDREF(*aMsgDatabase = mDatabase);
+  mDatabase->SetLastUseTime(PR_Now());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase *aMsgDatabase)
 {
   if (mDatabase)
   {
--- a/mailnews/db/msgdb/public/nsIMsgDatabase.idl
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -130,17 +130,17 @@ interface nsMsgDBCommitType
 [ptr] native nsMsgKeyArrayPtr(nsTArray<nsMsgKey>);
 
 /**
  * A service to open mail databases and manipulate listeners automatically.
  *
  * The contract ID for this component is
  * <tt>\@mozilla.org/msgDatabase/msgDBService;1</tt>.
  */
-[scriptable, uuid(09f52152-a3b7-4b89-935d-4033cbaea7e0)]
+[scriptable, uuid(e4743a99-ca44-4647-87b4-d73a444226b3)]
 interface nsIMsgDBService : nsISupports
 {
   /**
    * Opens a database for a given folder.
    *
    * This method is preferred over nsIMsgDBService::openMailDBFromFile if the
    * caller has an actual nsIMsgFolder around. If the database detects that it
    * is unreadable or out of date (using nsIMsgDatabase::outOfDate) it will
@@ -260,32 +260,49 @@ interface nsIMsgDBService : nsISupports
   /**
    * Get the db for a folder, if already open.
    *
    * @param aFolder   The folder to get the cached (open) db for.
    *
    * @returns         null if the db isn't open, otherwise the db.
    */
   nsIMsgDatabase cachedDBForFolder(in nsIMsgFolder aFolder);
+
+  /// an enumerator to iterate over the open dbs.
+  readonly attribute nsIArray openDBs;
 };
 
-[scriptable, uuid(a3cd60ac-d279-4521-8880-2e7723315cba)]
+[scriptable, uuid(21122b78-12ed-4657-aa07-29e109667825)]
 interface nsIMsgDatabase : nsIDBChangeAnnouncer {
   void forceFolderDBClosed(in nsIMsgFolder aFolder);
   void Close(in boolean aForceCommit);
 
   void Commit(in nsMsgDBCommit commitType);
   // Force closed is evil, and we should see if we can do without it.
   // In 4.x, it was mainly used to remove corrupted databases.
   void ForceClosed();
   void clearCachedHdrs();
   void resetHdrCacheSize(in unsigned long size);
 
   readonly attribute nsIDBFolderInfo  dBFolderInfo;
 
+  /// Folder this db was opened on.
+  readonly attribute nsIMsgFolder folder;
+  /**
+   * This is used when deciding which db's to close to free up memory
+   * and other resources in an LRU manner. It doesn't track every operation
+   * on every object from the db, but high level things like open, commit,
+   * and perhaps some of the list methods. Commit should be a proxy for all
+   * the mutation methods.
+   *
+   * I'm allowing clients to set the last use time as well, so that
+   * nsIMsgFolder.msgDatabase can set the last use time.
+   */
+  attribute PRTime lastUseTime;
+
   // get a message header for the given key. Caller must release()!
 
   nsIMsgDBHdr GetMsgHdrForKey(in nsMsgKey key);
   nsIMsgDBHdr getMsgHdrForMessageID(in string messageID);
   //Returns whether or not this database contains the given key
   boolean ContainsKey(in nsMsgKey key);
 
 /**
@@ -457,17 +474,17 @@ interface nsIMsgDatabase : nsIDBChangeAn
   // purge unwanted message headers and/or bodies. If deleteViaFolder is
   // true, we'll call nsIMsgFolder::DeleteMessages to delete the messages.
   // Otherwise, we'll just delete them from the db.
   void applyRetentionSettings(in nsIMsgRetentionSettings aMsgRetentionSettings, 
                               in boolean aDeleteViaFolder);
 
   attribute nsIMsgDownloadSettings msgDownloadSettings;
 
-  boolean HasNew();  
+  boolean HasNew();
   void ClearNewList(in boolean notify);
   void AddToNewList(in nsMsgKey key);
 
   // used mainly to force the timestamp of a local mail folder db to
   // match the time stamp of the corresponding berkeley mail folder,
   // but also useful to tell the summary to mark itself invalid
   // Also, if a local folder is being reparsed, summary will be invalid
   // until the reparsing is done.
--- a/mailnews/db/msgdb/public/nsMsgDatabase.h
+++ b/mailnews/db/msgdb/public/nsMsgDatabase.h
@@ -263,17 +263,22 @@ protected:
   virtual bool    UseStrictThreading();
   virtual bool    UseCorrectThreading();
   virtual nsresult ThreadNewHdr(nsMsgHdr* hdr, bool &newThread);
   virtual nsresult AddNewThread(nsMsgHdr *msgHdr);
   virtual nsresult AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *pMsgHdr, bool threadInThread);
 
   static nsTArray<nsMsgDatabase*>* m_dbCache;
   static nsTArray<nsMsgDatabase*>* GetDBCache();
-  
+
+  static PRTime gLastUseTime; // global last use time
+  PRTime m_lastUseTime;       // last use time for this db
+  // inline to make instrumentation as cheap as possible
+  inline void RememberLastUseTime() {gLastUseTime = m_lastUseTime = PR_Now();}
+
   static void    AddToCache(nsMsgDatabase* pMessageDB) 
   {
 #ifdef DEBUG_David_Bienvenu
 //    NS_ASSERTION(GetDBCache()->Length() < 50, "50 or more open db's");
 #endif
 #ifdef DEBUG
     nsCOMPtr<nsIMsgDatabase> msgDB = pMessageDB->m_folder ?
                                dont_AddRef(FindInCache(pMessageDB->m_folder)) : nsnull;
@@ -312,24 +317,25 @@ protected:
                                bool keepUnreadMessagesOnly,
                                bool applyToFlaggedMessages,
                                nsIMutableArray *hdrsToDelete);
   
   // mdb bookkeeping stuff
   virtual nsresult      InitExistingDB();
   virtual nsresult      InitNewDB();
   virtual nsresult      InitMDBInfo();
-  
+
   nsCOMPtr <nsIMsgFolder> m_folder;
   nsDBFolderInfo      *m_dbFolderInfo;
   nsMsgKey      m_nextPseudoMsgKey;
   nsIMdbEnv     *m_mdbEnv;  // to be used in all the db calls.
   nsIMdbStore   *m_mdbStore;
   nsIMdbTable   *m_mdbAllMsgHeadersTable;
   nsIMdbTable   *m_mdbAllThreadsTable;
+
   // Used for asynchronous db opens. If non-null, we're still opening
   // the underlying mork database. If null, the db has been completely opened.
   nsCOMPtr<nsIMdbThumb> m_thumb;
   // used to remember the args to Open for async open.
   bool m_create;
   bool m_leaveInvalidDB;
 
   nsCString     m_dbName;
--- a/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -75,16 +75,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsMemory.h"
 #include "nsICollation.h"
 #include "nsCollationCID.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIMsgPluggableStore.h"
 #include "nsAlgorithm.h"
+#include "nsArrayEnumerator.h"
 
 #if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
 #define DEBUG_MSGKEYSET 1
 #endif
 
 #define MSG_HASH_SIZE 512
 
 // This will be used on discovery, since we don't know total.
@@ -99,16 +100,18 @@ const PRUint32 kInitialMsgDBCacheSize = 
 static const nsMsgKey kAllMsgHdrsTableKey = 1;
 static const nsMsgKey kTableKeyForThreadOne = 0xfffffffe;
 static const nsMsgKey kAllThreadsTableKey = 0xfffffffd;
 static const nsMsgKey kFirstPseudoKey = 0xfffffff0;
 static const nsMsgKey kIdStartOfFake = 0xffffff80;
 
 static PRLogModuleInfo* DBLog;
 
+PRTime nsMsgDatabase::gLastUseTime;
+
 NS_IMPL_ISUPPORTS1(nsMsgDBService, nsIMsgDBService)
 
 nsMsgDBService::nsMsgDBService()
 {
   DBLog = PR_NewLogModule("MSGDB");
 }
 
 
@@ -134,16 +137,17 @@ NS_IMETHODIMP nsMsgDBService::OpenFolder
 
   nsMsgDatabase *cacheDB = nsMsgDatabase::FindInCache(summaryFilePath);
   if (cacheDB)
   {
     // this db could have ended up in the folder cache w/o an m_folder pointer via
     // OpenMailDBFromFile. If so, take this chance to fix the folder.
     if (!cacheDB->m_folder)
       cacheDB->m_folder = aFolder;
+    cacheDB->RememberLastUseTime();
     *_retval = cacheDB; // FindInCache already addRefed.
     // if m_thumb is set, someone is asynchronously opening the db. But our
     // caller wants to synchronously open it, so just do it.
     if (cacheDB->m_thumb)
       return cacheDB->Open(summaryFilePath, false, aLeaveInvalidDB);
     return NS_OK;
   }
 
@@ -331,16 +335,17 @@ void nsMsgDBService::FinishDBOpen(nsIMsg
     PRInt32 numMessages;
     aMsgDB->m_mdbAllMsgHeadersTable->GetCount(aMsgDB->GetEnv(),
                                               &numHdrsInTable);
     aMsgDB->m_dbFolderInfo->GetNumMessages(&numMessages);
     if (numMessages != (PRInt32) numHdrsInTable)
       aMsgDB->SyncCounts();
   }
   HookupPendingListeners(aMsgDB, aFolder);
+  aMsgDB->RememberLastUseTime();
 }
 
 // This method is called when the caller is trying to create a db without
 // having a corresponding nsIMsgFolder object.  This happens in a few
 // situations, including imap folder discovery, compacting local folders,
 // and copying local folders.
 NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsILocalFile *aFolderName,
                                                  nsIMsgFolder *aFolder,
@@ -444,16 +449,29 @@ NS_IMETHODIMP nsMsgDBService::Unregister
 NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder *aFolder, nsIMsgDatabase **aRetDB)
 {
   NS_ENSURE_ARG_POINTER(aFolder);
   NS_ENSURE_ARG_POINTER(aRetDB);
   *aRetDB = nsMsgDatabase::FindInCache(aFolder);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsMsgDBService::GetOpenDBs(nsIArray **aOpenDBs)
+{
+  NS_ENSURE_ARG_POINTER(aOpenDBs);
+  nsresult rv;
+  nsCOMPtr<nsIMutableArray> openDBs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+  for (PRUint32 i = 0; i < nsMsgDatabase::m_dbCache->Length(); i++)
+    openDBs->AppendElement(nsMsgDatabase::m_dbCache->ElementAt(i), false);
+
+  openDBs.forget(aOpenDBs);
+  return NS_OK;
+}
+
 static bool gGotGlobalPrefs = false;
 static bool gThreadWithoutRe = true;
 static bool gStrictThreading = false;
 static bool gCorrectThreading = false;
 
 void nsMsgDatabase::GetGlobalPrefs()
 {
   if (!gGotGlobalPrefs)
@@ -526,16 +544,29 @@ NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCa
 
 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrCacheSize(PRUint32 *aSize)
 {
   NS_ENSURE_ARG_POINTER(aSize);
   *aSize = m_cacheSize;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsMsgDatabase::GetLastUseTime(PRTime *aTime)
+{
+  NS_ENSURE_ARG_POINTER(aTime);
+  *aTime = m_lastUseTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime)
+{
+  gLastUseTime = m_lastUseTime = aTime;
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs()
 {
   ClearCachedObjects(false);
 #ifdef DEBUG_bienvenu1
   if (mRefCnt > 1)
   {
     NS_ASSERTION(false, "");
     printf("someone's holding onto db - refs = %ld\n", mRefCnt);
@@ -1085,35 +1116,17 @@ nsMsgDatabase::~nsMsgDatabase()
   if (m_mdbEnv)
   {
     m_mdbEnv->Release(); //??? is this right?
     m_mdbEnv = nsnull;
   }
   m_ChangeListeners.Clear();
 }
 
-NS_IMPL_ADDREF(nsMsgDatabase)
-
-NS_IMPL_RELEASE(nsMsgDatabase)
-
-NS_IMETHODIMP nsMsgDatabase::QueryInterface(REFNSIID aIID, void** aResult)
-{
-  if (aResult == NULL)
-    return NS_ERROR_NULL_POINTER;
-
-  if (aIID.Equals(NS_GET_IID(nsIMsgDatabase)) ||
-    aIID.Equals(NS_GET_IID(nsIDBChangeAnnouncer)) ||
-    aIID.Equals(NS_GET_IID(nsISupports)))
-  {
-    *aResult = static_cast<nsIMsgDatabase*>(this);
-    NS_ADDREF_THIS();
-    return NS_OK;
-  }
-  return NS_NOINTERFACE;
-}
+NS_IMPL_ISUPPORTS2(nsMsgDatabase, nsIMsgDatabase, nsIDBChangeAnnouncer);
 
 void nsMsgDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
 {
   if (!mMdbFactory)
   {
     nsresult rv;
     nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
     if (NS_SUCCEEDED(rv) && mdbFactoryService)
@@ -1434,25 +1447,29 @@ NS_IMETHODIMP nsMsgDatabase::GetDBFolder
   {
     NS_ERROR("db must be corrupt");
     return NS_ERROR_NULL_POINTER;
   }
   NS_ADDREF(*result = m_dbFolderInfo);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsMsgDatabase::GetFolder(nsIMsgFolder **aFolder)
+{
+  NS_ENSURE_ARG_POINTER(aFolder);
+  NS_IF_ADDREF(*aFolder = m_folder);
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsMsgDatabase::Commit(nsMsgDBCommit commitType)
 {
   nsresult  err = NS_OK;
-  nsIMdbThumb  *commitThumb = NULL;
-
-#ifdef DEBUG_seth
-  printf("nsMsgDatabase::Commit(%d)\n",commitType);
-#endif
-
+  nsCOMPtr<nsIMdbThumb> commitThumb;
+
+  RememberLastUseTime();
   if (commitType == nsMsgDBCommitType::kLargeCommit || commitType == nsMsgDBCommitType::kSessionCommit)
   {
     mdb_percent outActualWaste = 0;
     mdb_bool outShould;
     if (m_mdbStore) {
       err = m_mdbStore->ShouldCompress(GetEnv(), 30, &outActualWaste, &outShould);
       if (NS_SUCCEEDED(err) && outShould)
         commitType = nsMsgDBCommitType::kCompressCommit;
@@ -1460,38 +1477,37 @@ NS_IMETHODIMP nsMsgDatabase::Commit(nsMs
   }
   //  commitType = nsMsgDBCommitType::kCompressCommit;  // ### until incremental writing works.
 
   if (m_mdbStore)
   {
     switch (commitType)
     {
     case nsMsgDBCommitType::kLargeCommit:
-      err = m_mdbStore->LargeCommit(GetEnv(), &commitThumb);
+      err = m_mdbStore->LargeCommit(GetEnv(), getter_AddRefs(commitThumb));
       break;
     case nsMsgDBCommitType::kSessionCommit:
-      err = m_mdbStore->SessionCommit(GetEnv(), &commitThumb);
+      err = m_mdbStore->SessionCommit(GetEnv(), getter_AddRefs(commitThumb));
       break;
     case nsMsgDBCommitType::kCompressCommit:
-      err = m_mdbStore->CompressCommit(GetEnv(), &commitThumb);
+      err = m_mdbStore->CompressCommit(GetEnv(), getter_AddRefs(commitThumb));
       break;
     }
   }
   if (commitThumb)
   {
     mdb_count outTotal = 0;    // total somethings to do in operation
     mdb_count outCurrent = 0;  // subportion of total completed so far
     mdb_bool outDone = false;      // is operation finished?
     mdb_bool outBroken = false;     // is operation irreparably dead and broken?
     while (!outDone && !outBroken && err == NS_OK)
     {
       err = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken);
     }
 
-    NS_IF_RELEASE(commitThumb);
   }
   // ### do something with error, but clear it now because mork errors out on commits.
   if (GetEnv())
     GetEnv()->ClearErrors();
 
   nsresult rv;
   nsCOMPtr<nsIMsgAccountManager> accountManager =
     do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
@@ -1807,16 +1823,21 @@ NS_IMETHODIMP nsMsgDatabase::ContainsKey
 
 // get a message header for the given key. Caller must release()!
 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForKey(nsMsgKey key, nsIMsgDBHdr **pmsgHdr)
 {
   nsresult  err = NS_OK;
   mdb_bool  hasOid;
   mdbOid    rowObjectId;
 
+  // Because this may be called a lot, and we don't want gettimeofday() to show
+  // up in trace logs, we just remember the most recent time any db was used,
+  // which should be close enough for our purposes.
+  m_lastUseTime = gLastUseTime;
+
 #ifdef DEBUG_bienvenu1
   NS_ASSERTION(m_folder, "folder should be set");
 #endif
 
   if (!pmsgHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
     return NS_ERROR_NULL_POINTER;
 
   *pmsgHdr = NULL;
@@ -2922,16 +2943,17 @@ nsresult nsMsgFilteredDBEnumerator::Pref
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMETHODIMP
 nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result)
 {
+  RememberLastUseTime();
   NS_ENSURE_ARG_POINTER(result);
   nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
                                                nsnull, nsnull);
   if (!e)
     return NS_ERROR_OUT_OF_MEMORY;
   NS_ADDREF(*result = e);
   return NS_OK;
 }
@@ -3056,16 +3078,19 @@ nsMsgDatabase::SyncCounts()
 }
 
 // resulting output array is sorted by key.
 NS_IMETHODIMP nsMsgDatabase::ListAllKeys(nsIMsgKeyArray *aKeys)
 {
   NS_ENSURE_ARG_POINTER(aKeys);
   nsresult  rv = NS_OK;
   nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+
+  RememberLastUseTime();
+
   if (m_mdbAllMsgHeadersTable)
   {
     PRUint32 numMsgs = 0;
     m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs);
     aKeys->SetCapacity(numMsgs);
     rv = m_mdbAllMsgHeadersTable->GetTableRowCursor(GetEnv(), -1,
                                                      getter_AddRefs(rowCursor));
     while (NS_SUCCEEDED(rv) && rowCursor)
@@ -3298,16 +3323,17 @@ NS_IMETHODIMP nsMsgDBThreadEnumerator::H
     PrefetchNext();
   *aResult = !mDone;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMsgDatabase::EnumerateThreads(nsISimpleEnumerator* *result)
 {
+  RememberLastUseTime();
   nsMsgDBThreadEnumerator* e = new nsMsgDBThreadEnumerator(this, nsnull);
   if (e == nsnull)
     return NS_ERROR_OUT_OF_MEMORY;
   NS_ADDREF(*result = e);
   return NS_OK;
 }
 
 // only return headers with a particular flag set
@@ -3318,21 +3344,23 @@ nsMsgFlagSetFilter(nsIMsgDBHdr *msg, voi
   desiredFlags = * (PRUint32 *) closure;
   msg->GetFlags(&msgFlags);
   return (msgFlags & desiredFlags) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 nsMsgDatabase::EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, PRUint32 *pFlag)
 {
-    nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag);
-    if (e == nsnull)
-        return NS_ERROR_OUT_OF_MEMORY;
-    NS_ADDREF(*result = e);
-    return NS_OK;
+  RememberLastUseTime();
+
+  nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag);
+  if (!e)
+    return NS_ERROR_OUT_OF_MEMORY;
+  NS_ADDREF(*result = e);
+  return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgDatabase::CreateNewHdr(nsMsgKey key, nsIMsgDBHdr **pnewHdr)
 {
   nsresult  err = NS_OK;
   nsIMdbRow    *hdrRow;
   struct mdbOid allMsgHdrsTableOID;
 
--- a/mailnews/local/src/nsLocalMailFolder.cpp
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -337,16 +337,18 @@ NS_IMETHODIMP nsMsgLocalMailFolder::GetD
     rv = OpenDatabase();
     if (mDatabase)
     {
       mDatabase->AddListener(this);
       UpdateNewMessages();
     }
   }
   NS_IF_ADDREF(*aDatabase = mDatabase);
+  if (mDatabase)
+    mDatabase->SetLastUseTime(PR_Now());
   return rv;
 }
 
 
 // Makes sure the database is open and exists.  If the database is out of date,
 // then this call will run an async url to reparse the folder. The passed in
 // url listener will get called when the url is done.
 NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse(nsIUrlListener *aReparseUrlListener, nsIMsgWindow *aMsgWindow,
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -106,16 +106,20 @@ pref("mailnews.tcptimeout", 100);
 pref("mailnews.headers.showSender", false);
 
 // set to 0 if you don't want to ignore timestamp differences between
 // local mail folders and the value stored in the corresponding .msf file.
 // 0 was the default up to and including 1.5. I've made the default
 // be greater than one hour so daylight savings time changes don't affect us.
 // We will still always regenerate .msf files if the file size changes.
 pref("mail.db_timestamp_leeway", 4000);
+// How long should we leave idle db's open, in seconds.
+pref("mail.db.idle_limit", 3000);
+// How many db's should we leave open? LRU db's will be closed first
+pref("mail.db.max_open", 30);
 
 pref("mail.imap.chunk_size",                65536);
 pref("mail.imap.min_chunk_size_threshold",  98304);
 pref("mail.imap.chunk_fast",                2);
 pref("mail.imap.chunk_ideal",               4);
 pref("mail.imap.chunk_add",                 8192);
 pref("mail.imap.hide_other_users",          false);
 pref("mail.imap.hide_unused_namespaces",    true);