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/ErrorNames.h"
12 #include "mozilla/Services.h"
13 #include "mozilla/Preferences.h"
14 #include "nsIObserverService.h"
16 #include "nsThreadUtils.h"
17 #include "mozilla/Logging.h"
19 #include "mozilla/StaticPrefs_storage.h"
21 #include "mozStorageConnection.h"
22 #include "mozStoragePrivateHelpers.h"
23 #include "mozIStorageStatement.h"
24 #include "mozIStorageCompletionCallback.h"
25 #include "mozIStorageAsyncStatement.h"
26 #include "mozIStoragePendingStatement.h"
27 #include "mozIStorageError.h"
28 #include "mozStorageHelper.h"
29 #include "nsXULAppAPI.h"
30 #include "xpcpublic.h"
32 #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
34 // Used to notify the begin and end of a vacuum operation.
35 #define OBSERVER_TOPIC_VACUUM_BEGIN "vacuum-begin"
36 #define OBSERVER_TOPIC_VACUUM_END "vacuum-end"
37 // This notification is sent only in automation when vacuum for a database is
38 // skipped, and can thus be used to verify that.
39 #define OBSERVER_TOPIC_VACUUM_SKIP "vacuum-skip"
41 // This preferences root will contain last vacuum timestamps (in seconds) for
42 // each database. The database filename is used as a key.
43 #define PREF_VACUUM_BRANCH "storage.vacuum.last."
45 // Time between subsequent vacuum calls for a certain database.
46 #define VACUUM_INTERVAL_SECONDS (30 * 86400) // 30 days.
48 extern mozilla::LazyLogModule gStorageLog
;
50 namespace mozilla::storage
{
54 ////////////////////////////////////////////////////////////////////////////////
55 //// Vacuumer declaration.
57 class Vacuumer final
: public mozIStorageCompletionCallback
{
60 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
62 explicit Vacuumer(mozIStorageVacuumParticipant
* aParticipant
);
66 nsresult
notifyCompletion(bool aSucceeded
);
67 ~Vacuumer() = default;
69 nsCOMPtr
<mozIStorageVacuumParticipant
> mParticipant
;
70 nsCString mDBFilename
;
71 nsCOMPtr
<mozIStorageAsyncConnection
> mDBConn
;
74 ////////////////////////////////////////////////////////////////////////////////
75 //// Vacuumer implementation.
77 NS_IMPL_ISUPPORTS(Vacuumer
, mozIStorageCompletionCallback
)
79 Vacuumer::Vacuumer(mozIStorageVacuumParticipant
* aParticipant
)
80 : mParticipant(aParticipant
) {}
82 bool Vacuumer::execute() {
83 MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
85 // Get the connection and check its validity.
86 nsresult rv
= mParticipant
->GetDatabaseConnection(getter_AddRefs(mDBConn
));
87 if (NS_FAILED(rv
) || !mDBConn
) return false;
89 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
91 bool inAutomation
= xpc::IsInAutomation();
92 // Get the database filename. Last vacuum time is stored under this name
93 // in PREF_VACUUM_BRANCH.
94 nsCOMPtr
<nsIFile
> databaseFile
;
95 mDBConn
->GetDatabaseFile(getter_AddRefs(databaseFile
));
97 NS_WARNING("Trying to vacuum a in-memory database!");
98 if (inAutomation
&& os
) {
99 mozilla::Unused
<< os
->NotifyObservers(
100 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
, u
":memory:");
104 nsAutoString databaseFilename
;
105 rv
= databaseFile
->GetLeafName(databaseFilename
);
106 NS_ENSURE_SUCCESS(rv
, false);
107 CopyUTF16toUTF8(databaseFilename
, mDBFilename
);
108 MOZ_ASSERT(!mDBFilename
.IsEmpty(), "Database filename cannot be empty");
110 // Check interval from last vacuum.
111 int32_t now
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
113 nsAutoCString
prefName(PREF_VACUUM_BRANCH
);
114 prefName
+= mDBFilename
;
115 rv
= Preferences::GetInt(prefName
.get(), &lastVacuum
);
116 if (NS_SUCCEEDED(rv
) && (now
- lastVacuum
) < VACUUM_INTERVAL_SECONDS
) {
117 // This database was vacuumed recently, skip it.
118 if (inAutomation
&& os
) {
119 mozilla::Unused
<< os
->NotifyObservers(
120 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
,
121 NS_ConvertUTF8toUTF16(mDBFilename
).get());
126 // Notify that we are about to start vacuuming. The participant can opt-out
127 // if it cannot handle a vacuum at this time, and then we'll move to the next
129 bool vacuumGranted
= false;
130 rv
= mParticipant
->OnBeginVacuum(&vacuumGranted
);
131 NS_ENSURE_SUCCESS(rv
, false);
132 if (!vacuumGranted
) {
133 if (inAutomation
&& os
) {
134 mozilla::Unused
<< os
->NotifyObservers(
135 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
,
136 NS_ConvertUTF8toUTF16(mDBFilename
).get());
141 // Ask for the expected page size. Vacuum can change the page size, unless
142 // the database is using WAL journaling.
143 // TODO Bug 634374: figure out a strategy to fix page size with WAL.
144 int32_t expectedPageSize
= 0;
145 rv
= mParticipant
->GetExpectedDatabasePageSize(&expectedPageSize
);
146 if (NS_FAILED(rv
) || !Service::pageSizeIsValid(expectedPageSize
)) {
147 NS_WARNING("Invalid page size requested for database, won't set it. ");
148 NS_WARNING(mDBFilename
.get());
149 expectedPageSize
= 0;
152 bool incremental
= false;
153 mozilla::Unused
<< mParticipant
->GetUseIncrementalVacuum(&incremental
);
155 // Notify vacuum is about to start.
157 mozilla::Unused
<< os
->NotifyObservers(
158 nullptr, OBSERVER_TOPIC_VACUUM_BEGIN
,
159 NS_ConvertUTF8toUTF16(mDBFilename
).get());
162 rv
= mDBConn
->AsyncVacuum(this, incremental
, expectedPageSize
);
164 // The connection is not ready.
165 mozilla::Unused
<< Complete(rv
, nullptr);
173 Vacuumer::Complete(nsresult aStatus
, nsISupports
* aValue
) {
174 if (NS_SUCCEEDED(aStatus
)) {
175 // Update last vacuum time.
176 int32_t now
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
177 MOZ_ASSERT(!mDBFilename
.IsEmpty(), "Database filename cannot be empty");
178 nsAutoCString
prefName(PREF_VACUUM_BRANCH
);
179 prefName
+= mDBFilename
;
180 DebugOnly
<nsresult
> rv
= Preferences::SetInt(prefName
.get(), now
);
181 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Should be able to set a preference");
182 notifyCompletion(true);
186 nsAutoCString errName
;
187 GetErrorName(aStatus
, errName
);
188 nsCString errMsg
= nsPrintfCString(
189 "Vacuum failed on '%s' with error %s - code %" PRIX32
, mDBFilename
.get(),
190 errName
.get(), static_cast<uint32_t>(aStatus
));
191 NS_WARNING(errMsg
.get());
192 if (MOZ_LOG_TEST(gStorageLog
, LogLevel::Error
)) {
193 MOZ_LOG(gStorageLog
, LogLevel::Error
, ("%s", errMsg
.get()));
196 notifyCompletion(false);
200 nsresult
Vacuumer::notifyCompletion(bool aSucceeded
) {
201 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
203 mozilla::Unused
<< os
->NotifyObservers(
204 nullptr, OBSERVER_TOPIC_VACUUM_END
,
205 NS_ConvertUTF8toUTF16(mDBFilename
).get());
208 nsresult rv
= mParticipant
->OnEndVacuum(aSucceeded
);
209 NS_ENSURE_SUCCESS(rv
, rv
);
216 ////////////////////////////////////////////////////////////////////////////////
219 NS_IMPL_ISUPPORTS(VacuumManager
, nsIObserver
)
221 VacuumManager
* VacuumManager::gVacuumManager
= nullptr;
223 already_AddRefed
<VacuumManager
> VacuumManager::getSingleton() {
224 // Don't allocate it in the child Process.
225 if (!XRE_IsParentProcess()) {
229 if (!gVacuumManager
) {
230 auto manager
= MakeRefPtr
<VacuumManager
>();
231 MOZ_ASSERT(gVacuumManager
== manager
.get());
232 return manager
.forget();
234 return do_AddRef(gVacuumManager
);
237 VacuumManager::VacuumManager() : mParticipants("vacuum-participant") {
238 MOZ_ASSERT(!gVacuumManager
,
239 "Attempting to create two instances of the service!");
240 gVacuumManager
= this;
243 VacuumManager::~VacuumManager() {
244 // Remove the static reference to the service. Check to make sure its us
245 // in case somebody creates an extra instance of the service.
246 MOZ_ASSERT(gVacuumManager
== this,
247 "Deleting a non-singleton instance of the service");
248 if (gVacuumManager
== this) {
249 gVacuumManager
= nullptr;
253 ////////////////////////////////////////////////////////////////////////////////
257 VacuumManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
258 const char16_t
* aData
) {
259 if (strcmp(aTopic
, OBSERVER_TOPIC_IDLE_DAILY
) == 0) {
260 // Try to run vacuum on all registered entries. Will stop at the first
262 nsCOMArray
<mozIStorageVacuumParticipant
> entries
;
263 mParticipants
.GetEntries(entries
);
264 // If there are more entries than what a month can contain, we could end up
265 // skipping some, since we run daily. So we use a starting index.
266 static const char* kPrefName
= PREF_VACUUM_BRANCH
"index";
267 int32_t startIndex
= Preferences::GetInt(kPrefName
, 0);
268 if (startIndex
>= entries
.Count()) {
272 for (index
= startIndex
; index
< entries
.Count(); ++index
) {
273 RefPtr
<Vacuumer
> vacuum
= new Vacuumer(entries
[index
]);
274 // Only vacuum one database per day.
275 if (vacuum
->execute()) {
279 DebugOnly
<nsresult
> rv
= Preferences::SetInt(kPrefName
, index
);
280 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Should be able to set a preference");
286 } // namespace mozilla::storage