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"
18 #include "mozilla/IntegerPrintfMacros.h"
21 #include "mozStorageConnection.h"
22 #include "mozStoragePrivateHelpers.h"
23 #include "mozIStorageCompletionCallback.h"
24 #include "nsXULAppAPI.h"
25 #include "xpcpublic.h"
27 #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
29 // Used to notify the begin and end of a vacuum operation.
30 #define OBSERVER_TOPIC_VACUUM_BEGIN "vacuum-begin"
31 #define OBSERVER_TOPIC_VACUUM_END "vacuum-end"
32 // This notification is sent only in automation when vacuum for a database is
33 // skipped, and can thus be used to verify that.
34 #define OBSERVER_TOPIC_VACUUM_SKIP "vacuum-skip"
36 // This preferences root will contain last vacuum timestamps (in seconds) for
37 // each database. The database filename is used as a key.
38 #define PREF_VACUUM_BRANCH "storage.vacuum.last."
40 // Time between subsequent vacuum calls for a certain database.
41 #define VACUUM_INTERVAL_SECONDS (30 * 86400) // 30 days.
43 extern mozilla::LazyLogModule gStorageLog
;
45 namespace mozilla::storage
{
49 ////////////////////////////////////////////////////////////////////////////////
50 //// Vacuumer declaration.
52 class Vacuumer final
: public mozIStorageCompletionCallback
{
55 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
57 explicit Vacuumer(mozIStorageVacuumParticipant
* aParticipant
);
61 nsresult
notifyCompletion(bool aSucceeded
);
62 ~Vacuumer() = default;
64 nsCOMPtr
<mozIStorageVacuumParticipant
> mParticipant
;
65 nsCString mDBFilename
;
66 nsCOMPtr
<mozIStorageAsyncConnection
> mDBConn
;
69 ////////////////////////////////////////////////////////////////////////////////
70 //// Vacuumer implementation.
72 NS_IMPL_ISUPPORTS(Vacuumer
, mozIStorageCompletionCallback
)
74 Vacuumer::Vacuumer(mozIStorageVacuumParticipant
* aParticipant
)
75 : mParticipant(aParticipant
) {}
77 bool Vacuumer::execute() {
78 MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
80 // Get the connection and check its validity.
81 nsresult rv
= mParticipant
->GetDatabaseConnection(getter_AddRefs(mDBConn
));
82 if (NS_FAILED(rv
) || !mDBConn
) return false;
84 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
86 bool inAutomation
= xpc::IsInAutomation();
87 // Get the database filename. Last vacuum time is stored under this name
88 // in PREF_VACUUM_BRANCH.
89 nsCOMPtr
<nsIFile
> databaseFile
;
90 mDBConn
->GetDatabaseFile(getter_AddRefs(databaseFile
));
92 NS_WARNING("Trying to vacuum a in-memory database!");
93 if (inAutomation
&& os
) {
94 mozilla::Unused
<< os
->NotifyObservers(
95 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
, u
":memory:");
99 nsAutoString databaseFilename
;
100 rv
= databaseFile
->GetLeafName(databaseFilename
);
101 NS_ENSURE_SUCCESS(rv
, false);
102 CopyUTF16toUTF8(databaseFilename
, mDBFilename
);
103 MOZ_ASSERT(!mDBFilename
.IsEmpty(), "Database filename cannot be empty");
105 // Check interval from last vacuum.
106 int32_t now
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
108 nsAutoCString
prefName(PREF_VACUUM_BRANCH
);
109 prefName
+= mDBFilename
;
110 rv
= Preferences::GetInt(prefName
.get(), &lastVacuum
);
111 if (NS_SUCCEEDED(rv
) && (now
- lastVacuum
) < VACUUM_INTERVAL_SECONDS
) {
112 // This database was vacuumed recently, skip it.
113 if (inAutomation
&& os
) {
114 mozilla::Unused
<< os
->NotifyObservers(
115 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
,
116 NS_ConvertUTF8toUTF16(mDBFilename
).get());
121 // Notify that we are about to start vacuuming. The participant can opt-out
122 // if it cannot handle a vacuum at this time, and then we'll move to the next
124 bool vacuumGranted
= false;
125 rv
= mParticipant
->OnBeginVacuum(&vacuumGranted
);
126 NS_ENSURE_SUCCESS(rv
, false);
127 if (!vacuumGranted
) {
128 if (inAutomation
&& os
) {
129 mozilla::Unused
<< os
->NotifyObservers(
130 nullptr, OBSERVER_TOPIC_VACUUM_SKIP
,
131 NS_ConvertUTF8toUTF16(mDBFilename
).get());
136 // Ask for the expected page size. Vacuum can change the page size, unless
137 // the database is using WAL journaling.
138 // TODO Bug 634374: figure out a strategy to fix page size with WAL.
139 int32_t expectedPageSize
= 0;
140 rv
= mParticipant
->GetExpectedDatabasePageSize(&expectedPageSize
);
141 if (NS_FAILED(rv
) || !Service::pageSizeIsValid(expectedPageSize
)) {
142 NS_WARNING("Invalid page size requested for database, won't set it. ");
143 NS_WARNING(mDBFilename
.get());
144 expectedPageSize
= 0;
147 bool incremental
= false;
148 mozilla::Unused
<< mParticipant
->GetUseIncrementalVacuum(&incremental
);
150 // Notify vacuum is about to start.
152 mozilla::Unused
<< os
->NotifyObservers(
153 nullptr, OBSERVER_TOPIC_VACUUM_BEGIN
,
154 NS_ConvertUTF8toUTF16(mDBFilename
).get());
157 rv
= mDBConn
->AsyncVacuum(this, incremental
, expectedPageSize
);
159 // The connection is not ready.
160 mozilla::Unused
<< Complete(rv
, nullptr);
168 Vacuumer::Complete(nsresult aStatus
, nsISupports
* aValue
) {
169 if (NS_SUCCEEDED(aStatus
)) {
170 // Update last vacuum time.
171 int32_t now
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
172 MOZ_ASSERT(!mDBFilename
.IsEmpty(), "Database filename cannot be empty");
173 nsAutoCString
prefName(PREF_VACUUM_BRANCH
);
174 prefName
+= mDBFilename
;
175 DebugOnly
<nsresult
> rv
= Preferences::SetInt(prefName
.get(), now
);
176 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Should be able to set a preference");
177 notifyCompletion(true);
181 nsAutoCString errName
;
182 GetErrorName(aStatus
, errName
);
183 nsCString errMsg
= nsPrintfCString(
184 "Vacuum failed on '%s' with error %s - code %" PRIX32
, mDBFilename
.get(),
185 errName
.get(), static_cast<uint32_t>(aStatus
));
186 NS_WARNING(errMsg
.get());
187 if (MOZ_LOG_TEST(gStorageLog
, LogLevel::Error
)) {
188 MOZ_LOG(gStorageLog
, LogLevel::Error
, ("%s", errMsg
.get()));
191 notifyCompletion(false);
195 nsresult
Vacuumer::notifyCompletion(bool aSucceeded
) {
196 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
198 mozilla::Unused
<< os
->NotifyObservers(
199 nullptr, OBSERVER_TOPIC_VACUUM_END
,
200 NS_ConvertUTF8toUTF16(mDBFilename
).get());
203 nsresult rv
= mParticipant
->OnEndVacuum(aSucceeded
);
204 NS_ENSURE_SUCCESS(rv
, rv
);
211 ////////////////////////////////////////////////////////////////////////////////
214 NS_IMPL_ISUPPORTS(VacuumManager
, nsIObserver
)
216 VacuumManager
* VacuumManager::gVacuumManager
= nullptr;
218 already_AddRefed
<VacuumManager
> VacuumManager::getSingleton() {
219 // Don't allocate it in the child Process.
220 if (!XRE_IsParentProcess()) {
224 if (!gVacuumManager
) {
225 auto manager
= MakeRefPtr
<VacuumManager
>();
226 MOZ_ASSERT(gVacuumManager
== manager
.get());
227 return manager
.forget();
229 return do_AddRef(gVacuumManager
);
232 VacuumManager::VacuumManager() : mParticipants("vacuum-participant") {
233 MOZ_ASSERT(!gVacuumManager
,
234 "Attempting to create two instances of the service!");
235 gVacuumManager
= this;
238 VacuumManager::~VacuumManager() {
239 // Remove the static reference to the service. Check to make sure its us
240 // in case somebody creates an extra instance of the service.
241 MOZ_ASSERT(gVacuumManager
== this,
242 "Deleting a non-singleton instance of the service");
243 if (gVacuumManager
== this) {
244 gVacuumManager
= nullptr;
248 ////////////////////////////////////////////////////////////////////////////////
252 VacuumManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
253 const char16_t
* aData
) {
254 if (strcmp(aTopic
, OBSERVER_TOPIC_IDLE_DAILY
) == 0) {
255 // Try to run vacuum on all registered entries. Will stop at the first
257 nsCOMArray
<mozIStorageVacuumParticipant
> entries
;
258 mParticipants
.GetEntries(entries
);
259 // If there are more entries than what a month can contain, we could end up
260 // skipping some, since we run daily. So we use a starting index.
261 static const char* kPrefName
= PREF_VACUUM_BRANCH
"index";
262 int32_t startIndex
= Preferences::GetInt(kPrefName
, 0);
263 if (startIndex
>= entries
.Count()) {
267 for (index
= startIndex
; index
< entries
.Count(); ++index
) {
268 RefPtr
<Vacuumer
> vacuum
= new Vacuumer(entries
[index
]);
269 // Only vacuum one database per day.
270 if (vacuum
->execute()) {
274 DebugOnly
<nsresult
> rv
= Preferences::SetInt(kPrefName
, index
);
275 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Should be able to set a preference");
281 } // namespace mozilla::storage