storage/src/VacuumManager.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "mozilla/DebugOnly.h"
     9 #include "VacuumManager.h"
    11 #include "mozilla/Services.h"
    12 #include "mozilla/Preferences.h"
    13 #include "nsIObserverService.h"
    14 #include "nsIFile.h"
    15 #include "nsThreadUtils.h"
    16 #include "prlog.h"
    17 #include "prtime.h"
    19 #include "mozStorageConnection.h"
    20 #include "mozIStorageStatement.h"
    21 #include "mozIStorageAsyncStatement.h"
    22 #include "mozIStoragePendingStatement.h"
    23 #include "mozIStorageError.h"
    24 #include "mozStorageHelper.h"
    25 #include "nsXULAppAPI.h"
    27 #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
    28 #define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
    30 // Used to notify begin and end of a heavy IO task.
    31 #define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
    32 #define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin")
    33 #define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end")
    35 // This preferences root will contain last vacuum timestamps (in seconds) for
    36 // each database.  The database filename is used as a key.
    37 #define PREF_VACUUM_BRANCH "storage.vacuum.last."
    39 // Time between subsequent vacuum calls for a certain database.
    40 #define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
    42 #ifdef PR_LOGGING
    43 extern PRLogModuleInfo *gStorageLog;
    44 #endif
    46 namespace mozilla {
    47 namespace storage {
    49 namespace {
    51 ////////////////////////////////////////////////////////////////////////////////
    52 //// BaseCallback
    54 class BaseCallback : public mozIStorageStatementCallback
    55 {
    56 public:
    57   NS_DECL_ISUPPORTS
    58   NS_DECL_MOZISTORAGESTATEMENTCALLBACK
    59   BaseCallback() {}
    60 protected:
    61   virtual ~BaseCallback() {}
    62 };
    64 NS_IMETHODIMP
    65 BaseCallback::HandleError(mozIStorageError *aError)
    66 {
    67 #ifdef DEBUG
    68   int32_t result;
    69   nsresult rv = aError->GetResult(&result);
    70   NS_ENSURE_SUCCESS(rv, rv);
    71   nsAutoCString message;
    72   rv = aError->GetMessage(message);
    73   NS_ENSURE_SUCCESS(rv, rv);
    75   nsAutoCString warnMsg;
    76   warnMsg.AppendLiteral("An error occured during async execution: ");
    77   warnMsg.AppendInt(result);
    78   warnMsg.AppendLiteral(" ");
    79   warnMsg.Append(message);
    80   NS_WARNING(warnMsg.get());
    81 #endif
    82   return NS_OK;
    83 }
    85 NS_IMETHODIMP
    86 BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
    87 {
    88   // We could get results from PRAGMA statements, but we don't mind them.
    89   return NS_OK;
    90 }
    92 NS_IMETHODIMP
    93 BaseCallback::HandleCompletion(uint16_t aReason)
    94 {
    95   // By default BaseCallback will just be silent on completion.
    96   return NS_OK;
    97 }
    99 NS_IMPL_ISUPPORTS(
   100   BaseCallback
   101 , mozIStorageStatementCallback
   102 )
   104 //////////////////////////////////////////////////////////////////////////////// 
   105 //// Vacuumer declaration.
   107 class Vacuumer : public BaseCallback
   108 {
   109 public:
   110   NS_DECL_MOZISTORAGESTATEMENTCALLBACK
   112   Vacuumer(mozIStorageVacuumParticipant *aParticipant);
   114   bool execute();
   115   nsresult notifyCompletion(bool aSucceeded);
   117 private:
   118   nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
   119   nsCString mDBFilename;
   120   nsCOMPtr<mozIStorageConnection> mDBConn;
   121 };
   123 ////////////////////////////////////////////////////////////////////////////////
   124 //// Vacuumer implementation.
   126 Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant)
   127   : mParticipant(aParticipant)
   128 {
   129 }
   131 bool
   132 Vacuumer::execute()
   133 {
   134   MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
   136   // Get the connection and check its validity.
   137   nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
   138   NS_ENSURE_SUCCESS(rv, false);
   139   bool ready = false;
   140   if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
   141     NS_WARNING("Unable to get a connection to vacuum database");
   142     return false;
   143   }
   145   // Ask for the expected page size.  Vacuum can change the page size, unless
   146   // the database is using WAL journaling.
   147   // TODO Bug 634374: figure out a strategy to fix page size with WAL.
   148   int32_t expectedPageSize = 0;
   149   rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
   150   if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) {
   151     NS_WARNING("Invalid page size requested for database, will use default ");
   152     NS_WARNING(mDBFilename.get());
   153     expectedPageSize = Service::getDefaultPageSize();
   154   }
   156   // Get the database filename.  Last vacuum time is stored under this name
   157   // in PREF_VACUUM_BRANCH.
   158   nsCOMPtr<nsIFile> databaseFile;
   159   mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
   160   if (!databaseFile) {
   161     NS_WARNING("Trying to vacuum a in-memory database!");
   162     return false;
   163   }
   164   nsAutoString databaseFilename;
   165   rv = databaseFile->GetLeafName(databaseFilename);
   166   NS_ENSURE_SUCCESS(rv, false);
   167   mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
   168   MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
   170   // Check interval from last vacuum.
   171   int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
   172   int32_t lastVacuum;
   173   nsAutoCString prefName(PREF_VACUUM_BRANCH);
   174   prefName += mDBFilename;
   175   rv = Preferences::GetInt(prefName.get(), &lastVacuum);
   176   if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
   177     // This database was vacuumed recently, skip it.
   178     return false;
   179   }
   181   // Notify that we are about to start vacuuming.  The participant can opt-out
   182   // if it cannot handle a vacuum at this time, and then we'll move to the next
   183   // one.
   184   bool vacuumGranted = false;
   185   rv = mParticipant->OnBeginVacuum(&vacuumGranted);
   186   NS_ENSURE_SUCCESS(rv, false);
   187   if (!vacuumGranted) {
   188     return false;
   189   }
   191   // Notify a heavy IO task is about to start.
   192   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   193   if (os) {
   194     DebugOnly<nsresult> rv =
   195       os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
   196                           OBSERVER_DATA_VACUUM_BEGIN.get());
   197     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
   198   }
   200   // Execute the statements separately, since the pragma may conflict with the
   201   // vacuum, if they are executed in the same transaction.
   202   nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
   203   nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
   204                               "PRAGMA page_size = ");
   205   pageSizeQuery.AppendInt(expectedPageSize);
   206   rv = mDBConn->CreateAsyncStatement(pageSizeQuery,
   207                                      getter_AddRefs(pageSizeStmt));
   208   NS_ENSURE_SUCCESS(rv, false);
   209   nsRefPtr<BaseCallback> callback = new BaseCallback();
   210   nsCOMPtr<mozIStoragePendingStatement> ps;
   211   rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
   212   NS_ENSURE_SUCCESS(rv, false);
   214   nsCOMPtr<mozIStorageAsyncStatement> stmt;
   215   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
   216     "VACUUM"
   217   ), getter_AddRefs(stmt));
   218   NS_ENSURE_SUCCESS(rv, false);
   219   rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
   220   NS_ENSURE_SUCCESS(rv, false);
   222   return true;
   223 }
   225 ////////////////////////////////////////////////////////////////////////////////
   226 //// mozIStorageStatementCallback
   228 NS_IMETHODIMP
   229 Vacuumer::HandleError(mozIStorageError *aError)
   230 {
   231 #ifdef DEBUG
   232   int32_t result;
   233   nsresult rv = aError->GetResult(&result);
   234   NS_ENSURE_SUCCESS(rv, rv);
   235   nsAutoCString message;
   236   rv = aError->GetMessage(message);
   237   NS_ENSURE_SUCCESS(rv, rv);
   239   nsAutoCString warnMsg;
   240   warnMsg.AppendLiteral("Unable to vacuum database: ");
   241   warnMsg.Append(mDBFilename);
   242   warnMsg.AppendLiteral(" - ");
   243   warnMsg.AppendInt(result);
   244   warnMsg.AppendLiteral(" ");
   245   warnMsg.Append(message);
   246   NS_WARNING(warnMsg.get());
   247 #endif
   249 #ifdef PR_LOGGING
   250   {
   251     int32_t result;
   252     nsresult rv = aError->GetResult(&result);
   253     NS_ENSURE_SUCCESS(rv, rv);
   254     nsAutoCString message;
   255     rv = aError->GetMessage(message);
   256     NS_ENSURE_SUCCESS(rv, rv);
   257     PR_LOG(gStorageLog, PR_LOG_ERROR,
   258            ("Vacuum failed with error: %d '%s'. Database was: '%s'",
   259             result, message.get(), mDBFilename.get()));
   260   }
   261 #endif
   262   return NS_OK;
   263 }
   265 NS_IMETHODIMP
   266 Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
   267 {
   268   NS_NOTREACHED("Got a resultset from a vacuum?");
   269   return NS_OK;
   270 }
   272 NS_IMETHODIMP
   273 Vacuumer::HandleCompletion(uint16_t aReason)
   274 {
   275   if (aReason == REASON_FINISHED) {
   276     // Update last vacuum time.
   277     int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
   278     MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
   279     nsAutoCString prefName(PREF_VACUUM_BRANCH);
   280     prefName += mDBFilename;
   281     DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
   282     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); 
   283   }
   285   notifyCompletion(aReason == REASON_FINISHED);
   287   return NS_OK;
   288 }
   290 nsresult
   291 Vacuumer::notifyCompletion(bool aSucceeded)
   292 {
   293   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   294   if (os) {
   295     os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
   296                         OBSERVER_DATA_VACUUM_END.get());
   297   }
   299   nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
   300   NS_ENSURE_SUCCESS(rv, rv);
   302   return NS_OK;
   303 }
   305 } // Anonymous namespace.
   307 ////////////////////////////////////////////////////////////////////////////////
   308 //// VacuumManager
   310 NS_IMPL_ISUPPORTS(
   311   VacuumManager
   312 , nsIObserver
   313 )
   315 VacuumManager *
   316 VacuumManager::gVacuumManager = nullptr;
   318 VacuumManager *
   319 VacuumManager::getSingleton()
   320 {
   321   //Don't allocate it in the child Process.
   322   if (XRE_GetProcessType() != GeckoProcessType_Default) {
   323     return nullptr;
   324   }
   326   if (gVacuumManager) {
   327     NS_ADDREF(gVacuumManager);
   328     return gVacuumManager;
   329   }
   330   gVacuumManager = new VacuumManager();
   331   if (gVacuumManager) {
   332     NS_ADDREF(gVacuumManager);
   333   }
   334   return gVacuumManager;
   335 }
   337 VacuumManager::VacuumManager()
   338   : mParticipants("vacuum-participant")
   339 {
   340   MOZ_ASSERT(!gVacuumManager,
   341              "Attempting to create two instances of the service!");
   342   gVacuumManager = this;
   343 }
   345 VacuumManager::~VacuumManager()
   346 {
   347   // Remove the static reference to the service.  Check to make sure its us
   348   // in case somebody creates an extra instance of the service.
   349   MOZ_ASSERT(gVacuumManager == this,
   350              "Deleting a non-singleton instance of the service");
   351   if (gVacuumManager == this) {
   352     gVacuumManager = nullptr;
   353   }
   354 }
   356 ////////////////////////////////////////////////////////////////////////////////
   357 //// nsIObserver
   359 NS_IMETHODIMP
   360 VacuumManager::Observe(nsISupports *aSubject,
   361                        const char *aTopic,
   362                        const char16_t *aData)
   363 {
   364   if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
   365     // Try to run vacuum on all registered entries.  Will stop at the first
   366     // successful one.
   367     nsCOMArray<mozIStorageVacuumParticipant> entries;
   368     mParticipants.GetEntries(entries);
   369     // If there are more entries than what a month can contain, we could end up
   370     // skipping some, since we run daily.  So we use a starting index.
   371     static const char* kPrefName = PREF_VACUUM_BRANCH "index";
   372     int32_t startIndex = Preferences::GetInt(kPrefName, 0);
   373     if (startIndex >= entries.Count()) {
   374       startIndex = 0;
   375     }
   376     int32_t index;
   377     for (index = startIndex; index < entries.Count(); ++index) {
   378       nsRefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
   379       // Only vacuum one database per day.
   380       if (vacuum->execute()) {
   381         break;
   382       }
   383     }
   384     DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
   385     MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
   386   }
   388   return NS_OK;
   389 }
   391 } // namespace storage
   392 } // namespace mozilla

mercurial