LCOV - code coverage report
Current view: directory - storage/src - VacuumManager.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 154 119 77.3 %
Date: 2012-06-02 Functions: 24 21 87.5 %

       1                 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2                 :  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
       3                 :  * ***** BEGIN LICENSE BLOCK *****
       4                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
       5                 :  *
       6                 :  * The contents of this file are subject to the Mozilla Public License Version
       7                 :  * 1.1 (the "License"); you may not use this file except in compliance with
       8                 :  * the License. You may obtain a copy of the License at
       9                 :  * http://www.mozilla.org/MPL/
      10                 :  *
      11                 :  * Software distributed under the License is distributed on an "AS IS" basis,
      12                 :  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
      13                 :  * for the specific language governing rights and limitations under the
      14                 :  * License.
      15                 :  *
      16                 :  * The Original Code is mozStorage.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is
      19                 :  * the Mozilla Foundation.
      20                 :  * Portions created by the Initial Developer are Copyright (C) 2010
      21                 :  * the Initial Developer. All Rights Reserved.
      22                 :  *
      23                 :  * Contributor(s):
      24                 :  *   Marco Bonardo <mak77@bonardo.net> (Original Author)
      25                 :  *
      26                 :  * Alternatively, the contents of this file may be used under the terms of
      27                 :  * either the GNU General Public License Version 2 or later (the "GPL"), or
      28                 :  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
      29                 :  * in which case the provisions of the GPL or the LGPL are applicable instead
      30                 :  * of those above. If you wish to allow use of your version of this file only
      31                 :  * under the terms of either the GPL or the LGPL, and not to allow others to
      32                 :  * use your version of this file under the terms of the MPL, indicate your
      33                 :  * decision by deleting the provisions above and replace them with the notice
      34                 :  * and other provisions required by the GPL or the LGPL. If you do not delete
      35                 :  * the provisions above, a recipient may use your version of this file under
      36                 :  * the terms of any one of the MPL, the GPL or the LGPL.
      37                 :  *
      38                 :  * ***** END LICENSE BLOCK ***** */
      39                 : 
      40                 : #include "VacuumManager.h"
      41                 : 
      42                 : #include "mozilla/Services.h"
      43                 : #include "mozilla/Preferences.h"
      44                 : #include "nsIObserverService.h"
      45                 : #include "nsPrintfCString.h"
      46                 : #include "nsIFile.h"
      47                 : #include "nsThreadUtils.h"
      48                 : #include "prlog.h"
      49                 : 
      50                 : #include "mozStorageConnection.h"
      51                 : #include "mozIStorageStatement.h"
      52                 : #include "mozIStorageAsyncStatement.h"
      53                 : #include "mozIStoragePendingStatement.h"
      54                 : #include "mozIStorageError.h"
      55                 : #include "mozStorageHelper.h"
      56                 : 
      57                 : #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
      58                 : #define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
      59                 : 
      60                 : // Used to notify begin and end of a heavy IO task.
      61                 : #define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
      62                 : #define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin")
      63                 : #define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end")
      64                 : 
      65                 : // This preferences root will contain last vacuum timestamps (in seconds) for
      66                 : // each database.  The database filename is used as a key.
      67                 : #define PREF_VACUUM_BRANCH "storage.vacuum.last."
      68                 : 
      69                 : // Time between subsequent vacuum calls for a certain database.
      70                 : #define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
      71                 : 
      72                 : #ifdef PR_LOGGING
      73                 : extern PRLogModuleInfo *gStorageLog;
      74                 : #endif
      75                 : 
      76                 : namespace mozilla {
      77                 : namespace storage {
      78                 : 
      79                 : namespace {
      80                 : 
      81                 : ////////////////////////////////////////////////////////////////////////////////
      82                 : //// BaseCallback
      83                 : 
      84                 : class BaseCallback : public mozIStorageStatementCallback
      85                 : {
      86                 : public:
      87                 :   NS_DECL_ISUPPORTS
      88                 :   NS_DECL_MOZISTORAGESTATEMENTCALLBACK
      89               5 :   BaseCallback() {}
      90                 : protected:
      91              12 :   virtual ~BaseCallback() {}
      92                 : };
      93                 : 
      94                 : NS_IMETHODIMP
      95               1 : BaseCallback::HandleError(mozIStorageError *aError)
      96                 : {
      97                 : #ifdef DEBUG
      98                 :   PRInt32 result;
      99               1 :   nsresult rv = aError->GetResult(&result);
     100               1 :   NS_ENSURE_SUCCESS(rv, rv);
     101               2 :   nsCAutoString message;
     102               1 :   rv = aError->GetMessage(message);
     103               1 :   NS_ENSURE_SUCCESS(rv, rv);
     104                 : 
     105               2 :   nsCAutoString warnMsg;
     106               1 :   warnMsg.AppendLiteral("An error occured during async execution: ");
     107               1 :   warnMsg.AppendInt(result);
     108               1 :   warnMsg.AppendLiteral(" ");
     109               1 :   warnMsg.Append(message);
     110               1 :   NS_WARNING(warnMsg.get());
     111                 : #endif
     112               1 :   return NS_OK;
     113                 : }
     114                 : 
     115                 : NS_IMETHODIMP
     116               0 : BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
     117                 : {
     118                 :   // We could get results from PRAGMA statements, but we don't mind them.
     119               0 :   return NS_OK;
     120                 : }
     121                 : 
     122                 : NS_IMETHODIMP
     123               1 : BaseCallback::HandleCompletion(PRUint16 aReason)
     124                 : {
     125                 :   // By default BaseCallback will just be silent on completion.
     126               1 :   return NS_OK;
     127                 : }
     128                 : 
     129              32 : NS_IMPL_ISUPPORTS1(
     130                 :   BaseCallback
     131                 : , mozIStorageStatementCallback
     132                 : )
     133                 : 
     134                 : //////////////////////////////////////////////////////////////////////////////// 
     135                 : //// Vacuumer declaration.
     136                 : 
     137                 : class Vacuumer : public BaseCallback
     138              16 : {
     139                 : public:
     140                 :   NS_DECL_MOZISTORAGESTATEMENTCALLBACK
     141                 : 
     142                 :   Vacuumer(mozIStorageVacuumParticipant *aParticipant);
     143                 : 
     144                 :   bool execute();
     145                 :   nsresult notifyCompletion(bool aSucceeded);
     146                 : 
     147                 : private:
     148                 :   nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
     149                 :   nsCString mDBFilename;
     150                 :   nsCOMPtr<mozIStorageConnection> mDBConn;
     151                 : };
     152                 : 
     153                 : ////////////////////////////////////////////////////////////////////////////////
     154                 : //// Vacuumer implementation.
     155                 : 
     156               4 : Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant)
     157               4 :   : mParticipant(aParticipant)
     158                 : {
     159               4 : }
     160                 : 
     161                 : bool
     162               4 : Vacuumer::execute()
     163                 : {
     164               4 :   MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
     165                 : 
     166                 :   // Get the connection and check its validity.
     167               4 :   nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
     168               4 :   NS_ENSURE_SUCCESS(rv, false);
     169               4 :   bool ready = false;
     170               4 :   if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
     171               0 :     NS_WARNING("Unable to get a connection to vacuum database");
     172               0 :     return false;
     173                 :   }
     174                 : 
     175                 :   // Ask for the expected page size.  Vacuum can change the page size, unless
     176                 :   // the database is using WAL journaling.
     177                 :   // TODO Bug 634374: figure out a strategy to fix page size with WAL.
     178               4 :   PRInt32 expectedPageSize = 0;
     179               4 :   rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
     180               4 :   if (NS_FAILED(rv) || expectedPageSize < 512 || expectedPageSize > 65536) {
     181               0 :     NS_WARNING("Invalid page size requested for database, will use default ");
     182               0 :     NS_WARNING(mDBFilename.get());
     183               0 :     expectedPageSize = mozIStorageConnection::DEFAULT_PAGE_SIZE;
     184                 :   }
     185                 : 
     186                 :   // Get the database filename.  Last vacuum time is stored under this name
     187                 :   // in PREF_VACUUM_BRANCH.
     188               8 :   nsCOMPtr<nsIFile> databaseFile;
     189               4 :   mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
     190               4 :   if (!databaseFile) {
     191               1 :     NS_WARNING("Trying to vacuum a in-memory database!");
     192               1 :     return false;
     193                 :   }
     194               6 :   nsAutoString databaseFilename;
     195               3 :   rv = databaseFile->GetLeafName(databaseFilename);
     196               3 :   NS_ENSURE_SUCCESS(rv, false);
     197               3 :   mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
     198               3 :   MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
     199                 : 
     200                 :   // Check interval from last vacuum.
     201               3 :   PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
     202                 :   PRInt32 lastVacuum;
     203               6 :   nsCAutoString prefName(PREF_VACUUM_BRANCH);
     204               3 :   prefName += mDBFilename;
     205               3 :   rv = Preferences::GetInt(prefName.get(), &lastVacuum);
     206               3 :   if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
     207                 :     // This database was vacuumed recently, skip it.
     208               1 :     return false;
     209                 :   }
     210                 : 
     211                 :   // Notify that we are about to start vacuuming.  The participant can opt-out
     212                 :   // if it cannot handle a vacuum at this time, and then we'll move to the next
     213                 :   // one.
     214               2 :   bool vacuumGranted = false;
     215               2 :   rv = mParticipant->OnBeginVacuum(&vacuumGranted);
     216               2 :   NS_ENSURE_SUCCESS(rv, false);
     217               2 :   if (!vacuumGranted) {
     218               1 :     return false;
     219                 :   }
     220                 : 
     221                 :   // Notify a heavy IO task is about to start.
     222               2 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     223               1 :   if (os) {
     224                 :     DebugOnly<nsresult> rv =
     225               1 :       os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
     226               2 :                           OBSERVER_DATA_VACUUM_BEGIN.get());
     227               1 :     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
     228                 :   }
     229                 : 
     230                 :   // Execute the statements separately, since the pragma may conflict with the
     231                 :   // vacuum, if they are executed in the same transaction.
     232               2 :   nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
     233               1 :   rv = mDBConn->CreateAsyncStatement(nsPrintfCString(
     234                 :     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size = %ld", expectedPageSize
     235               1 :   ), getter_AddRefs(pageSizeStmt));
     236               1 :   NS_ENSURE_SUCCESS(rv, false);
     237               2 :   nsCOMPtr<BaseCallback> callback = new BaseCallback();
     238               2 :   nsCOMPtr<mozIStoragePendingStatement> ps;
     239               1 :   rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
     240               1 :   NS_ENSURE_SUCCESS(rv, false);
     241                 : 
     242               2 :   nsCOMPtr<mozIStorageAsyncStatement> stmt;
     243               2 :   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     244                 :     "VACUUM"
     245               2 :   ), getter_AddRefs(stmt));
     246               1 :   NS_ENSURE_SUCCESS(rv, false);
     247               1 :   rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
     248               1 :   NS_ENSURE_SUCCESS(rv, false);
     249                 : 
     250               1 :   return true;
     251                 : }
     252                 : 
     253                 : ////////////////////////////////////////////////////////////////////////////////
     254                 : //// mozIStorageStatementCallback
     255                 : 
     256                 : NS_IMETHODIMP
     257               0 : Vacuumer::HandleError(mozIStorageError *aError)
     258                 : {
     259                 : #ifdef DEBUG
     260                 :   PRInt32 result;
     261               0 :   nsresult rv = aError->GetResult(&result);
     262               0 :   NS_ENSURE_SUCCESS(rv, rv);
     263               0 :   nsCAutoString message;
     264               0 :   rv = aError->GetMessage(message);
     265               0 :   NS_ENSURE_SUCCESS(rv, rv);
     266                 : 
     267               0 :   nsCAutoString warnMsg;
     268               0 :   warnMsg.AppendLiteral("Unable to vacuum database: ");
     269               0 :   warnMsg.Append(mDBFilename);
     270               0 :   warnMsg.AppendLiteral(" - ");
     271               0 :   warnMsg.AppendInt(result);
     272               0 :   warnMsg.AppendLiteral(" ");
     273               0 :   warnMsg.Append(message);
     274               0 :   NS_WARNING(warnMsg.get());
     275                 : #endif
     276                 : 
     277                 : #ifdef PR_LOGGING
     278                 :   {
     279                 :     PRInt32 result;
     280               0 :     nsresult rv = aError->GetResult(&result);
     281               0 :     NS_ENSURE_SUCCESS(rv, rv);
     282               0 :     nsCAutoString message;
     283               0 :     rv = aError->GetMessage(message);
     284               0 :     NS_ENSURE_SUCCESS(rv, rv);
     285               0 :     PR_LOG(gStorageLog, PR_LOG_ERROR,
     286                 :            ("Vacuum failed with error: %d '%s'. Database was: '%s'",
     287                 :             result, message.get(), mDBFilename.get()));
     288                 :   }
     289                 : #endif
     290               0 :   return NS_OK;
     291                 : }
     292                 : 
     293                 : NS_IMETHODIMP
     294               0 : Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
     295                 : {
     296               0 :   NS_NOTREACHED("Got a resultset from a vacuum?");
     297               0 :   return NS_OK;
     298                 : }
     299                 : 
     300                 : NS_IMETHODIMP
     301               1 : Vacuumer::HandleCompletion(PRUint16 aReason)
     302                 : {
     303               1 :   if (aReason == REASON_FINISHED) {
     304                 :     // Update last vacuum time.
     305               1 :     PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
     306               1 :     MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
     307               2 :     nsCAutoString prefName(PREF_VACUUM_BRANCH);
     308               1 :     prefName += mDBFilename;
     309               2 :     DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
     310               1 :     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); 
     311                 :   }
     312                 : 
     313               1 :   notifyCompletion(aReason == REASON_FINISHED);
     314                 : 
     315               1 :   return NS_OK;
     316                 : }
     317                 : 
     318                 : nsresult
     319               1 : Vacuumer::notifyCompletion(bool aSucceeded)
     320                 : {
     321               2 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     322               1 :   if (os) {
     323               1 :     os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
     324               1 :                         OBSERVER_DATA_VACUUM_END.get());
     325                 :   }
     326                 : 
     327               1 :   nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
     328               1 :   NS_ENSURE_SUCCESS(rv, rv);
     329                 : 
     330               1 :   return NS_OK;
     331                 : }
     332                 : 
     333                 : } // Anonymous namespace.
     334                 : 
     335                 : ////////////////////////////////////////////////////////////////////////////////
     336                 : //// VacuumManager
     337                 : 
     338              49 : NS_IMPL_ISUPPORTS1(
     339                 :   VacuumManager
     340                 : , nsIObserver
     341                 : )
     342                 : 
     343                 : VacuumManager *
     344                 : VacuumManager::gVacuumManager = nsnull;
     345                 : 
     346                 : VacuumManager *
     347               1 : VacuumManager::getSingleton()
     348                 : {
     349               1 :   if (gVacuumManager) {
     350               0 :     NS_ADDREF(gVacuumManager);
     351               0 :     return gVacuumManager;
     352                 :   }
     353               1 :   gVacuumManager = new VacuumManager();
     354               1 :   if (gVacuumManager) {
     355               1 :     NS_ADDREF(gVacuumManager);
     356                 :   }
     357               1 :   return gVacuumManager;
     358                 : }
     359                 : 
     360               1 : VacuumManager::VacuumManager()
     361               1 :   : mParticipants("vacuum-participant")
     362                 : {
     363               0 :   MOZ_ASSERT(!gVacuumManager,
     364               1 :              "Attempting to create two instances of the service!");
     365               1 :   gVacuumManager = this;
     366               1 : }
     367                 : 
     368               2 : VacuumManager::~VacuumManager()
     369                 : {
     370                 :   // Remove the static reference to the service.  Check to make sure its us
     371                 :   // in case somebody creates an extra instance of the service.
     372               0 :   MOZ_ASSERT(gVacuumManager == this,
     373               1 :              "Deleting a non-singleton instance of the service");
     374               1 :   if (gVacuumManager == this) {
     375               1 :     gVacuumManager = nsnull;
     376                 :   }
     377               1 : }
     378                 : 
     379                 : ////////////////////////////////////////////////////////////////////////////////
     380                 : //// nsIObserver
     381                 : 
     382                 : NS_IMETHODIMP
     383               4 : VacuumManager::Observe(nsISupports *aSubject,
     384                 :                        const char *aTopic,
     385                 :                        const PRUnichar *aData)
     386                 : {
     387               4 :   if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
     388                 :     // Try to run vacuum on all registered entries.  Will stop at the first
     389                 :     // successful one.
     390                 :     const nsCOMArray<mozIStorageVacuumParticipant> &entries =
     391               4 :       mParticipants.GetEntries();
     392                 :     // If there are more entries than what a month can contain, we could end up
     393                 :     // skipping some, since we run daily.  So we use a starting index.
     394                 :     static const char* kPrefName = PREF_VACUUM_BRANCH "index";
     395               4 :     PRInt32 startIndex = Preferences::GetInt(kPrefName, 0);
     396               4 :     if (startIndex >= entries.Count()) {
     397               2 :       startIndex = 0;
     398                 :     }
     399                 :     PRInt32 index;
     400               7 :     for (index = startIndex; index < entries.Count(); ++index) {
     401              12 :       nsCOMPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
     402                 :       // Only vacuum one database per day.
     403               4 :       if (vacuum->execute()) {
     404                 :         break;
     405                 :       }
     406                 :     }
     407               8 :     DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
     408               4 :     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
     409                 :   }
     410                 : 
     411               4 :   return NS_OK;
     412                 : }
     413                 : 
     414                 : } // namespace storage
     415                 : } // namespace mozilla

Generated by: LCOV version 1.7