1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/storage/src/VacuumManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,392 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/DebugOnly.h" 1.11 + 1.12 +#include "VacuumManager.h" 1.13 + 1.14 +#include "mozilla/Services.h" 1.15 +#include "mozilla/Preferences.h" 1.16 +#include "nsIObserverService.h" 1.17 +#include "nsIFile.h" 1.18 +#include "nsThreadUtils.h" 1.19 +#include "prlog.h" 1.20 +#include "prtime.h" 1.21 + 1.22 +#include "mozStorageConnection.h" 1.23 +#include "mozIStorageStatement.h" 1.24 +#include "mozIStorageAsyncStatement.h" 1.25 +#include "mozIStoragePendingStatement.h" 1.26 +#include "mozIStorageError.h" 1.27 +#include "mozStorageHelper.h" 1.28 +#include "nsXULAppAPI.h" 1.29 + 1.30 +#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily" 1.31 +#define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown" 1.32 + 1.33 +// Used to notify begin and end of a heavy IO task. 1.34 +#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task" 1.35 +#define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin") 1.36 +#define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end") 1.37 + 1.38 +// This preferences root will contain last vacuum timestamps (in seconds) for 1.39 +// each database. The database filename is used as a key. 1.40 +#define PREF_VACUUM_BRANCH "storage.vacuum.last." 1.41 + 1.42 +// Time between subsequent vacuum calls for a certain database. 1.43 +#define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days. 1.44 + 1.45 +#ifdef PR_LOGGING 1.46 +extern PRLogModuleInfo *gStorageLog; 1.47 +#endif 1.48 + 1.49 +namespace mozilla { 1.50 +namespace storage { 1.51 + 1.52 +namespace { 1.53 + 1.54 +//////////////////////////////////////////////////////////////////////////////// 1.55 +//// BaseCallback 1.56 + 1.57 +class BaseCallback : public mozIStorageStatementCallback 1.58 +{ 1.59 +public: 1.60 + NS_DECL_ISUPPORTS 1.61 + NS_DECL_MOZISTORAGESTATEMENTCALLBACK 1.62 + BaseCallback() {} 1.63 +protected: 1.64 + virtual ~BaseCallback() {} 1.65 +}; 1.66 + 1.67 +NS_IMETHODIMP 1.68 +BaseCallback::HandleError(mozIStorageError *aError) 1.69 +{ 1.70 +#ifdef DEBUG 1.71 + int32_t result; 1.72 + nsresult rv = aError->GetResult(&result); 1.73 + NS_ENSURE_SUCCESS(rv, rv); 1.74 + nsAutoCString message; 1.75 + rv = aError->GetMessage(message); 1.76 + NS_ENSURE_SUCCESS(rv, rv); 1.77 + 1.78 + nsAutoCString warnMsg; 1.79 + warnMsg.AppendLiteral("An error occured during async execution: "); 1.80 + warnMsg.AppendInt(result); 1.81 + warnMsg.AppendLiteral(" "); 1.82 + warnMsg.Append(message); 1.83 + NS_WARNING(warnMsg.get()); 1.84 +#endif 1.85 + return NS_OK; 1.86 +} 1.87 + 1.88 +NS_IMETHODIMP 1.89 +BaseCallback::HandleResult(mozIStorageResultSet *aResultSet) 1.90 +{ 1.91 + // We could get results from PRAGMA statements, but we don't mind them. 1.92 + return NS_OK; 1.93 +} 1.94 + 1.95 +NS_IMETHODIMP 1.96 +BaseCallback::HandleCompletion(uint16_t aReason) 1.97 +{ 1.98 + // By default BaseCallback will just be silent on completion. 1.99 + return NS_OK; 1.100 +} 1.101 + 1.102 +NS_IMPL_ISUPPORTS( 1.103 + BaseCallback 1.104 +, mozIStorageStatementCallback 1.105 +) 1.106 + 1.107 +//////////////////////////////////////////////////////////////////////////////// 1.108 +//// Vacuumer declaration. 1.109 + 1.110 +class Vacuumer : public BaseCallback 1.111 +{ 1.112 +public: 1.113 + NS_DECL_MOZISTORAGESTATEMENTCALLBACK 1.114 + 1.115 + Vacuumer(mozIStorageVacuumParticipant *aParticipant); 1.116 + 1.117 + bool execute(); 1.118 + nsresult notifyCompletion(bool aSucceeded); 1.119 + 1.120 +private: 1.121 + nsCOMPtr<mozIStorageVacuumParticipant> mParticipant; 1.122 + nsCString mDBFilename; 1.123 + nsCOMPtr<mozIStorageConnection> mDBConn; 1.124 +}; 1.125 + 1.126 +//////////////////////////////////////////////////////////////////////////////// 1.127 +//// Vacuumer implementation. 1.128 + 1.129 +Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant) 1.130 + : mParticipant(aParticipant) 1.131 +{ 1.132 +} 1.133 + 1.134 +bool 1.135 +Vacuumer::execute() 1.136 +{ 1.137 + MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!"); 1.138 + 1.139 + // Get the connection and check its validity. 1.140 + nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn)); 1.141 + NS_ENSURE_SUCCESS(rv, false); 1.142 + bool ready = false; 1.143 + if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) { 1.144 + NS_WARNING("Unable to get a connection to vacuum database"); 1.145 + return false; 1.146 + } 1.147 + 1.148 + // Ask for the expected page size. Vacuum can change the page size, unless 1.149 + // the database is using WAL journaling. 1.150 + // TODO Bug 634374: figure out a strategy to fix page size with WAL. 1.151 + int32_t expectedPageSize = 0; 1.152 + rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize); 1.153 + if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) { 1.154 + NS_WARNING("Invalid page size requested for database, will use default "); 1.155 + NS_WARNING(mDBFilename.get()); 1.156 + expectedPageSize = Service::getDefaultPageSize(); 1.157 + } 1.158 + 1.159 + // Get the database filename. Last vacuum time is stored under this name 1.160 + // in PREF_VACUUM_BRANCH. 1.161 + nsCOMPtr<nsIFile> databaseFile; 1.162 + mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile)); 1.163 + if (!databaseFile) { 1.164 + NS_WARNING("Trying to vacuum a in-memory database!"); 1.165 + return false; 1.166 + } 1.167 + nsAutoString databaseFilename; 1.168 + rv = databaseFile->GetLeafName(databaseFilename); 1.169 + NS_ENSURE_SUCCESS(rv, false); 1.170 + mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename); 1.171 + MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty"); 1.172 + 1.173 + // Check interval from last vacuum. 1.174 + int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); 1.175 + int32_t lastVacuum; 1.176 + nsAutoCString prefName(PREF_VACUUM_BRANCH); 1.177 + prefName += mDBFilename; 1.178 + rv = Preferences::GetInt(prefName.get(), &lastVacuum); 1.179 + if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) { 1.180 + // This database was vacuumed recently, skip it. 1.181 + return false; 1.182 + } 1.183 + 1.184 + // Notify that we are about to start vacuuming. The participant can opt-out 1.185 + // if it cannot handle a vacuum at this time, and then we'll move to the next 1.186 + // one. 1.187 + bool vacuumGranted = false; 1.188 + rv = mParticipant->OnBeginVacuum(&vacuumGranted); 1.189 + NS_ENSURE_SUCCESS(rv, false); 1.190 + if (!vacuumGranted) { 1.191 + return false; 1.192 + } 1.193 + 1.194 + // Notify a heavy IO task is about to start. 1.195 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.196 + if (os) { 1.197 + DebugOnly<nsresult> rv = 1.198 + os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO, 1.199 + OBSERVER_DATA_VACUUM_BEGIN.get()); 1.200 + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify"); 1.201 + } 1.202 + 1.203 + // Execute the statements separately, since the pragma may conflict with the 1.204 + // vacuum, if they are executed in the same transaction. 1.205 + nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt; 1.206 + nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR 1.207 + "PRAGMA page_size = "); 1.208 + pageSizeQuery.AppendInt(expectedPageSize); 1.209 + rv = mDBConn->CreateAsyncStatement(pageSizeQuery, 1.210 + getter_AddRefs(pageSizeStmt)); 1.211 + NS_ENSURE_SUCCESS(rv, false); 1.212 + nsRefPtr<BaseCallback> callback = new BaseCallback(); 1.213 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.214 + rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps)); 1.215 + NS_ENSURE_SUCCESS(rv, false); 1.216 + 1.217 + nsCOMPtr<mozIStorageAsyncStatement> stmt; 1.218 + rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( 1.219 + "VACUUM" 1.220 + ), getter_AddRefs(stmt)); 1.221 + NS_ENSURE_SUCCESS(rv, false); 1.222 + rv = stmt->ExecuteAsync(this, getter_AddRefs(ps)); 1.223 + NS_ENSURE_SUCCESS(rv, false); 1.224 + 1.225 + return true; 1.226 +} 1.227 + 1.228 +//////////////////////////////////////////////////////////////////////////////// 1.229 +//// mozIStorageStatementCallback 1.230 + 1.231 +NS_IMETHODIMP 1.232 +Vacuumer::HandleError(mozIStorageError *aError) 1.233 +{ 1.234 +#ifdef DEBUG 1.235 + int32_t result; 1.236 + nsresult rv = aError->GetResult(&result); 1.237 + NS_ENSURE_SUCCESS(rv, rv); 1.238 + nsAutoCString message; 1.239 + rv = aError->GetMessage(message); 1.240 + NS_ENSURE_SUCCESS(rv, rv); 1.241 + 1.242 + nsAutoCString warnMsg; 1.243 + warnMsg.AppendLiteral("Unable to vacuum database: "); 1.244 + warnMsg.Append(mDBFilename); 1.245 + warnMsg.AppendLiteral(" - "); 1.246 + warnMsg.AppendInt(result); 1.247 + warnMsg.AppendLiteral(" "); 1.248 + warnMsg.Append(message); 1.249 + NS_WARNING(warnMsg.get()); 1.250 +#endif 1.251 + 1.252 +#ifdef PR_LOGGING 1.253 + { 1.254 + int32_t result; 1.255 + nsresult rv = aError->GetResult(&result); 1.256 + NS_ENSURE_SUCCESS(rv, rv); 1.257 + nsAutoCString message; 1.258 + rv = aError->GetMessage(message); 1.259 + NS_ENSURE_SUCCESS(rv, rv); 1.260 + PR_LOG(gStorageLog, PR_LOG_ERROR, 1.261 + ("Vacuum failed with error: %d '%s'. Database was: '%s'", 1.262 + result, message.get(), mDBFilename.get())); 1.263 + } 1.264 +#endif 1.265 + return NS_OK; 1.266 +} 1.267 + 1.268 +NS_IMETHODIMP 1.269 +Vacuumer::HandleResult(mozIStorageResultSet *aResultSet) 1.270 +{ 1.271 + NS_NOTREACHED("Got a resultset from a vacuum?"); 1.272 + return NS_OK; 1.273 +} 1.274 + 1.275 +NS_IMETHODIMP 1.276 +Vacuumer::HandleCompletion(uint16_t aReason) 1.277 +{ 1.278 + if (aReason == REASON_FINISHED) { 1.279 + // Update last vacuum time. 1.280 + int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); 1.281 + MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty"); 1.282 + nsAutoCString prefName(PREF_VACUUM_BRANCH); 1.283 + prefName += mDBFilename; 1.284 + DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now); 1.285 + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); 1.286 + } 1.287 + 1.288 + notifyCompletion(aReason == REASON_FINISHED); 1.289 + 1.290 + return NS_OK; 1.291 +} 1.292 + 1.293 +nsresult 1.294 +Vacuumer::notifyCompletion(bool aSucceeded) 1.295 +{ 1.296 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.297 + if (os) { 1.298 + os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO, 1.299 + OBSERVER_DATA_VACUUM_END.get()); 1.300 + } 1.301 + 1.302 + nsresult rv = mParticipant->OnEndVacuum(aSucceeded); 1.303 + NS_ENSURE_SUCCESS(rv, rv); 1.304 + 1.305 + return NS_OK; 1.306 +} 1.307 + 1.308 +} // Anonymous namespace. 1.309 + 1.310 +//////////////////////////////////////////////////////////////////////////////// 1.311 +//// VacuumManager 1.312 + 1.313 +NS_IMPL_ISUPPORTS( 1.314 + VacuumManager 1.315 +, nsIObserver 1.316 +) 1.317 + 1.318 +VacuumManager * 1.319 +VacuumManager::gVacuumManager = nullptr; 1.320 + 1.321 +VacuumManager * 1.322 +VacuumManager::getSingleton() 1.323 +{ 1.324 + //Don't allocate it in the child Process. 1.325 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.326 + return nullptr; 1.327 + } 1.328 + 1.329 + if (gVacuumManager) { 1.330 + NS_ADDREF(gVacuumManager); 1.331 + return gVacuumManager; 1.332 + } 1.333 + gVacuumManager = new VacuumManager(); 1.334 + if (gVacuumManager) { 1.335 + NS_ADDREF(gVacuumManager); 1.336 + } 1.337 + return gVacuumManager; 1.338 +} 1.339 + 1.340 +VacuumManager::VacuumManager() 1.341 + : mParticipants("vacuum-participant") 1.342 +{ 1.343 + MOZ_ASSERT(!gVacuumManager, 1.344 + "Attempting to create two instances of the service!"); 1.345 + gVacuumManager = this; 1.346 +} 1.347 + 1.348 +VacuumManager::~VacuumManager() 1.349 +{ 1.350 + // Remove the static reference to the service. Check to make sure its us 1.351 + // in case somebody creates an extra instance of the service. 1.352 + MOZ_ASSERT(gVacuumManager == this, 1.353 + "Deleting a non-singleton instance of the service"); 1.354 + if (gVacuumManager == this) { 1.355 + gVacuumManager = nullptr; 1.356 + } 1.357 +} 1.358 + 1.359 +//////////////////////////////////////////////////////////////////////////////// 1.360 +//// nsIObserver 1.361 + 1.362 +NS_IMETHODIMP 1.363 +VacuumManager::Observe(nsISupports *aSubject, 1.364 + const char *aTopic, 1.365 + const char16_t *aData) 1.366 +{ 1.367 + if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) { 1.368 + // Try to run vacuum on all registered entries. Will stop at the first 1.369 + // successful one. 1.370 + nsCOMArray<mozIStorageVacuumParticipant> entries; 1.371 + mParticipants.GetEntries(entries); 1.372 + // If there are more entries than what a month can contain, we could end up 1.373 + // skipping some, since we run daily. So we use a starting index. 1.374 + static const char* kPrefName = PREF_VACUUM_BRANCH "index"; 1.375 + int32_t startIndex = Preferences::GetInt(kPrefName, 0); 1.376 + if (startIndex >= entries.Count()) { 1.377 + startIndex = 0; 1.378 + } 1.379 + int32_t index; 1.380 + for (index = startIndex; index < entries.Count(); ++index) { 1.381 + nsRefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]); 1.382 + // Only vacuum one database per day. 1.383 + if (vacuum->execute()) { 1.384 + break; 1.385 + } 1.386 + } 1.387 + DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index); 1.388 + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference"); 1.389 + } 1.390 + 1.391 + return NS_OK; 1.392 +} 1.393 + 1.394 +} // namespace storage 1.395 +} // namespace mozilla