Bug 1610796 [wpt PR 21339] - Update wpt metadata, a=testonly
[gecko.git] / storage / VacuumManager.cpp
blobd184a08add7773bc19c1982f431e29ef81b2e955
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 "mozilla/Logging.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 u"vacuum-begin"
33 #define OBSERVER_DATA_VACUUM_END u"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 extern mozilla::LazyLogModule gStorageLog;
44 namespace mozilla {
45 namespace storage {
47 namespace {
49 ////////////////////////////////////////////////////////////////////////////////
50 //// BaseCallback
52 class BaseCallback : public mozIStorageStatementCallback {
53 public:
54 NS_DECL_ISUPPORTS
55 NS_DECL_MOZISTORAGESTATEMENTCALLBACK
56 BaseCallback() {}
58 protected:
59 virtual ~BaseCallback() {}
62 NS_IMETHODIMP
63 BaseCallback::HandleError(mozIStorageError* aError) {
64 #ifdef DEBUG
65 int32_t result;
66 nsresult rv = aError->GetResult(&result);
67 NS_ENSURE_SUCCESS(rv, rv);
68 nsAutoCString message;
69 rv = aError->GetMessage(message);
70 NS_ENSURE_SUCCESS(rv, rv);
72 nsAutoCString warnMsg;
73 warnMsg.AppendLiteral("An error occured during async execution: ");
74 warnMsg.AppendInt(result);
75 warnMsg.Append(' ');
76 warnMsg.Append(message);
77 NS_WARNING(warnMsg.get());
78 #endif
79 return NS_OK;
82 NS_IMETHODIMP
83 BaseCallback::HandleResult(mozIStorageResultSet* aResultSet) {
84 // We could get results from PRAGMA statements, but we don't mind them.
85 return NS_OK;
88 NS_IMETHODIMP
89 BaseCallback::HandleCompletion(uint16_t aReason) {
90 // By default BaseCallback will just be silent on completion.
91 return NS_OK;
94 NS_IMPL_ISUPPORTS(BaseCallback, mozIStorageStatementCallback)
96 ////////////////////////////////////////////////////////////////////////////////
97 //// Vacuumer declaration.
99 class Vacuumer : public BaseCallback {
100 public:
101 NS_DECL_MOZISTORAGESTATEMENTCALLBACK
103 explicit Vacuumer(mozIStorageVacuumParticipant* aParticipant);
105 bool execute();
106 nsresult notifyCompletion(bool aSucceeded);
108 private:
109 nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
110 nsCString mDBFilename;
111 nsCOMPtr<mozIStorageConnection> mDBConn;
114 ////////////////////////////////////////////////////////////////////////////////
115 //// Vacuumer implementation.
117 Vacuumer::Vacuumer(mozIStorageVacuumParticipant* aParticipant)
118 : mParticipant(aParticipant) {}
120 bool Vacuumer::execute() {
121 MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
123 // Get the connection and check its validity.
124 nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
125 NS_ENSURE_SUCCESS(rv, false);
126 bool ready = false;
127 if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
128 NS_WARNING("Unable to get a connection to vacuum database");
129 return false;
132 // Ask for the expected page size. Vacuum can change the page size, unless
133 // the database is using WAL journaling.
134 // TODO Bug 634374: figure out a strategy to fix page size with WAL.
135 int32_t expectedPageSize = 0;
136 rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
137 if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) {
138 NS_WARNING("Invalid page size requested for database, will use default ");
139 NS_WARNING(mDBFilename.get());
140 expectedPageSize = Service::getDefaultPageSize();
143 // Get the database filename. Last vacuum time is stored under this name
144 // in PREF_VACUUM_BRANCH.
145 nsCOMPtr<nsIFile> databaseFile;
146 mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
147 if (!databaseFile) {
148 NS_WARNING("Trying to vacuum a in-memory database!");
149 return false;
151 nsAutoString databaseFilename;
152 rv = databaseFile->GetLeafName(databaseFilename);
153 NS_ENSURE_SUCCESS(rv, false);
154 mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
155 MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
157 // Check interval from last vacuum.
158 int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
159 int32_t lastVacuum;
160 nsAutoCString prefName(PREF_VACUUM_BRANCH);
161 prefName += mDBFilename;
162 rv = Preferences::GetInt(prefName.get(), &lastVacuum);
163 if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
164 // This database was vacuumed recently, skip it.
165 return false;
168 // Notify that we are about to start vacuuming. The participant can opt-out
169 // if it cannot handle a vacuum at this time, and then we'll move to the next
170 // one.
171 bool vacuumGranted = false;
172 rv = mParticipant->OnBeginVacuum(&vacuumGranted);
173 NS_ENSURE_SUCCESS(rv, false);
174 if (!vacuumGranted) {
175 return false;
178 // Notify a heavy IO task is about to start.
179 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
180 if (os) {
181 rv = os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
182 OBSERVER_DATA_VACUUM_BEGIN);
183 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
186 // Execute the statements separately, since the pragma may conflict with the
187 // vacuum, if they are executed in the same transaction.
188 nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
189 nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
190 "PRAGMA page_size = ");
191 pageSizeQuery.AppendInt(expectedPageSize);
192 rv = mDBConn->CreateAsyncStatement(pageSizeQuery,
193 getter_AddRefs(pageSizeStmt));
194 NS_ENSURE_SUCCESS(rv, false);
195 RefPtr<BaseCallback> callback = new BaseCallback();
196 nsCOMPtr<mozIStoragePendingStatement> ps;
197 rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
198 NS_ENSURE_SUCCESS(rv, false);
200 nsCOMPtr<mozIStorageAsyncStatement> stmt;
201 rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING("VACUUM"),
202 getter_AddRefs(stmt));
203 NS_ENSURE_SUCCESS(rv, false);
204 rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
205 NS_ENSURE_SUCCESS(rv, false);
207 return true;
210 ////////////////////////////////////////////////////////////////////////////////
211 //// mozIStorageStatementCallback
213 NS_IMETHODIMP
214 Vacuumer::HandleError(mozIStorageError* aError) {
215 int32_t result;
216 nsresult rv;
217 nsAutoCString message;
219 #ifdef DEBUG
220 rv = aError->GetResult(&result);
221 NS_ENSURE_SUCCESS(rv, rv);
222 rv = aError->GetMessage(message);
223 NS_ENSURE_SUCCESS(rv, rv);
225 nsAutoCString warnMsg;
226 warnMsg.AppendLiteral("Unable to vacuum database: ");
227 warnMsg.Append(mDBFilename);
228 warnMsg.AppendLiteral(" - ");
229 warnMsg.AppendInt(result);
230 warnMsg.Append(' ');
231 warnMsg.Append(message);
232 NS_WARNING(warnMsg.get());
233 #endif
235 if (MOZ_LOG_TEST(gStorageLog, LogLevel::Error)) {
236 rv = aError->GetResult(&result);
237 NS_ENSURE_SUCCESS(rv, rv);
238 rv = aError->GetMessage(message);
239 NS_ENSURE_SUCCESS(rv, rv);
240 MOZ_LOG(gStorageLog, LogLevel::Error,
241 ("Vacuum failed with error: %d '%s'. Database was: '%s'", result,
242 message.get(), mDBFilename.get()));
244 return NS_OK;
247 NS_IMETHODIMP
248 Vacuumer::HandleResult(mozIStorageResultSet* aResultSet) {
249 MOZ_ASSERT_UNREACHABLE("Got a resultset from a vacuum?");
250 return NS_OK;
253 NS_IMETHODIMP
254 Vacuumer::HandleCompletion(uint16_t aReason) {
255 if (aReason == REASON_FINISHED) {
256 // Update last vacuum time.
257 int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
258 MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
259 nsAutoCString prefName(PREF_VACUUM_BRANCH);
260 prefName += mDBFilename;
261 DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
262 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
265 notifyCompletion(aReason == REASON_FINISHED);
267 return NS_OK;
270 nsresult Vacuumer::notifyCompletion(bool aSucceeded) {
271 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
272 if (os) {
273 os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
274 OBSERVER_DATA_VACUUM_END);
277 nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
278 NS_ENSURE_SUCCESS(rv, rv);
280 return NS_OK;
283 } // namespace
285 ////////////////////////////////////////////////////////////////////////////////
286 //// VacuumManager
288 NS_IMPL_ISUPPORTS(VacuumManager, nsIObserver)
290 VacuumManager* VacuumManager::gVacuumManager = nullptr;
292 already_AddRefed<VacuumManager> VacuumManager::getSingleton() {
293 // Don't allocate it in the child Process.
294 if (!XRE_IsParentProcess()) {
295 return nullptr;
298 if (!gVacuumManager) {
299 auto manager = MakeRefPtr<VacuumManager>();
300 MOZ_ASSERT(gVacuumManager == manager.get());
301 return manager.forget();
303 return do_AddRef(gVacuumManager);
306 VacuumManager::VacuumManager() : mParticipants("vacuum-participant") {
307 MOZ_ASSERT(!gVacuumManager,
308 "Attempting to create two instances of the service!");
309 gVacuumManager = this;
312 VacuumManager::~VacuumManager() {
313 // Remove the static reference to the service. Check to make sure its us
314 // in case somebody creates an extra instance of the service.
315 MOZ_ASSERT(gVacuumManager == this,
316 "Deleting a non-singleton instance of the service");
317 if (gVacuumManager == this) {
318 gVacuumManager = nullptr;
322 ////////////////////////////////////////////////////////////////////////////////
323 //// nsIObserver
325 NS_IMETHODIMP
326 VacuumManager::Observe(nsISupports* aSubject, const char* aTopic,
327 const char16_t* aData) {
328 if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
329 // Try to run vacuum on all registered entries. Will stop at the first
330 // successful one.
331 nsCOMArray<mozIStorageVacuumParticipant> entries;
332 mParticipants.GetEntries(entries);
333 // If there are more entries than what a month can contain, we could end up
334 // skipping some, since we run daily. So we use a starting index.
335 static const char* kPrefName = PREF_VACUUM_BRANCH "index";
336 int32_t startIndex = Preferences::GetInt(kPrefName, 0);
337 if (startIndex >= entries.Count()) {
338 startIndex = 0;
340 int32_t index;
341 for (index = startIndex; index < entries.Count(); ++index) {
342 RefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
343 // Only vacuum one database per day.
344 if (vacuum->execute()) {
345 break;
348 DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
349 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
352 return NS_OK;
355 } // namespace storage
356 } // namespace mozilla