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