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 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozStorage.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * the Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Marco Bonardo <mak77@bonardo.net> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "VacuumManager.h"
41 :
42 : #include "mozilla/Services.h"
43 : #include "mozilla/Preferences.h"
44 : #include "nsIObserverService.h"
45 : #include "nsPrintfCString.h"
46 : #include "nsIFile.h"
47 : #include "nsThreadUtils.h"
48 : #include "prlog.h"
49 :
50 : #include "mozStorageConnection.h"
51 : #include "mozIStorageStatement.h"
52 : #include "mozIStorageAsyncStatement.h"
53 : #include "mozIStoragePendingStatement.h"
54 : #include "mozIStorageError.h"
55 : #include "mozStorageHelper.h"
56 :
57 : #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
58 : #define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
59 :
60 : // Used to notify begin and end of a heavy IO task.
61 : #define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
62 : #define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin")
63 : #define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end")
64 :
65 : // This preferences root will contain last vacuum timestamps (in seconds) for
66 : // each database. The database filename is used as a key.
67 : #define PREF_VACUUM_BRANCH "storage.vacuum.last."
68 :
69 : // Time between subsequent vacuum calls for a certain database.
70 : #define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
71 :
72 : #ifdef PR_LOGGING
73 : extern PRLogModuleInfo *gStorageLog;
74 : #endif
75 :
76 : namespace mozilla {
77 : namespace storage {
78 :
79 : namespace {
80 :
81 : ////////////////////////////////////////////////////////////////////////////////
82 : //// BaseCallback
83 :
84 : class BaseCallback : public mozIStorageStatementCallback
85 : {
86 : public:
87 : NS_DECL_ISUPPORTS
88 : NS_DECL_MOZISTORAGESTATEMENTCALLBACK
89 5 : BaseCallback() {}
90 : protected:
91 12 : virtual ~BaseCallback() {}
92 : };
93 :
94 : NS_IMETHODIMP
95 1 : BaseCallback::HandleError(mozIStorageError *aError)
96 : {
97 : #ifdef DEBUG
98 : PRInt32 result;
99 1 : nsresult rv = aError->GetResult(&result);
100 1 : NS_ENSURE_SUCCESS(rv, rv);
101 2 : nsCAutoString message;
102 1 : rv = aError->GetMessage(message);
103 1 : NS_ENSURE_SUCCESS(rv, rv);
104 :
105 2 : nsCAutoString warnMsg;
106 1 : warnMsg.AppendLiteral("An error occured during async execution: ");
107 1 : warnMsg.AppendInt(result);
108 1 : warnMsg.AppendLiteral(" ");
109 1 : warnMsg.Append(message);
110 1 : NS_WARNING(warnMsg.get());
111 : #endif
112 1 : return NS_OK;
113 : }
114 :
115 : NS_IMETHODIMP
116 0 : BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
117 : {
118 : // We could get results from PRAGMA statements, but we don't mind them.
119 0 : return NS_OK;
120 : }
121 :
122 : NS_IMETHODIMP
123 1 : BaseCallback::HandleCompletion(PRUint16 aReason)
124 : {
125 : // By default BaseCallback will just be silent on completion.
126 1 : return NS_OK;
127 : }
128 :
129 32 : NS_IMPL_ISUPPORTS1(
130 : BaseCallback
131 : , mozIStorageStatementCallback
132 : )
133 :
134 : ////////////////////////////////////////////////////////////////////////////////
135 : //// Vacuumer declaration.
136 :
137 : class Vacuumer : public BaseCallback
138 16 : {
139 : public:
140 : NS_DECL_MOZISTORAGESTATEMENTCALLBACK
141 :
142 : Vacuumer(mozIStorageVacuumParticipant *aParticipant);
143 :
144 : bool execute();
145 : nsresult notifyCompletion(bool aSucceeded);
146 :
147 : private:
148 : nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
149 : nsCString mDBFilename;
150 : nsCOMPtr<mozIStorageConnection> mDBConn;
151 : };
152 :
153 : ////////////////////////////////////////////////////////////////////////////////
154 : //// Vacuumer implementation.
155 :
156 4 : Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant)
157 4 : : mParticipant(aParticipant)
158 : {
159 4 : }
160 :
161 : bool
162 4 : Vacuumer::execute()
163 : {
164 4 : MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
165 :
166 : // Get the connection and check its validity.
167 4 : nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
168 4 : NS_ENSURE_SUCCESS(rv, false);
169 4 : bool ready = false;
170 4 : if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
171 0 : NS_WARNING("Unable to get a connection to vacuum database");
172 0 : return false;
173 : }
174 :
175 : // Ask for the expected page size. Vacuum can change the page size, unless
176 : // the database is using WAL journaling.
177 : // TODO Bug 634374: figure out a strategy to fix page size with WAL.
178 4 : PRInt32 expectedPageSize = 0;
179 4 : rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
180 4 : if (NS_FAILED(rv) || expectedPageSize < 512 || expectedPageSize > 65536) {
181 0 : NS_WARNING("Invalid page size requested for database, will use default ");
182 0 : NS_WARNING(mDBFilename.get());
183 0 : expectedPageSize = mozIStorageConnection::DEFAULT_PAGE_SIZE;
184 : }
185 :
186 : // Get the database filename. Last vacuum time is stored under this name
187 : // in PREF_VACUUM_BRANCH.
188 8 : nsCOMPtr<nsIFile> databaseFile;
189 4 : mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
190 4 : if (!databaseFile) {
191 1 : NS_WARNING("Trying to vacuum a in-memory database!");
192 1 : return false;
193 : }
194 6 : nsAutoString databaseFilename;
195 3 : rv = databaseFile->GetLeafName(databaseFilename);
196 3 : NS_ENSURE_SUCCESS(rv, false);
197 3 : mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
198 3 : MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
199 :
200 : // Check interval from last vacuum.
201 3 : PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
202 : PRInt32 lastVacuum;
203 6 : nsCAutoString prefName(PREF_VACUUM_BRANCH);
204 3 : prefName += mDBFilename;
205 3 : rv = Preferences::GetInt(prefName.get(), &lastVacuum);
206 3 : if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
207 : // This database was vacuumed recently, skip it.
208 1 : return false;
209 : }
210 :
211 : // Notify that we are about to start vacuuming. The participant can opt-out
212 : // if it cannot handle a vacuum at this time, and then we'll move to the next
213 : // one.
214 2 : bool vacuumGranted = false;
215 2 : rv = mParticipant->OnBeginVacuum(&vacuumGranted);
216 2 : NS_ENSURE_SUCCESS(rv, false);
217 2 : if (!vacuumGranted) {
218 1 : return false;
219 : }
220 :
221 : // Notify a heavy IO task is about to start.
222 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
223 1 : if (os) {
224 : DebugOnly<nsresult> rv =
225 1 : os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
226 2 : OBSERVER_DATA_VACUUM_BEGIN.get());
227 1 : MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
228 : }
229 :
230 : // Execute the statements separately, since the pragma may conflict with the
231 : // vacuum, if they are executed in the same transaction.
232 2 : nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
233 1 : rv = mDBConn->CreateAsyncStatement(nsPrintfCString(
234 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size = %ld", expectedPageSize
235 1 : ), getter_AddRefs(pageSizeStmt));
236 1 : NS_ENSURE_SUCCESS(rv, false);
237 2 : nsCOMPtr<BaseCallback> callback = new BaseCallback();
238 2 : nsCOMPtr<mozIStoragePendingStatement> ps;
239 1 : rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
240 1 : NS_ENSURE_SUCCESS(rv, false);
241 :
242 2 : nsCOMPtr<mozIStorageAsyncStatement> stmt;
243 2 : rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
244 : "VACUUM"
245 2 : ), getter_AddRefs(stmt));
246 1 : NS_ENSURE_SUCCESS(rv, false);
247 1 : rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
248 1 : NS_ENSURE_SUCCESS(rv, false);
249 :
250 1 : return true;
251 : }
252 :
253 : ////////////////////////////////////////////////////////////////////////////////
254 : //// mozIStorageStatementCallback
255 :
256 : NS_IMETHODIMP
257 0 : Vacuumer::HandleError(mozIStorageError *aError)
258 : {
259 : #ifdef DEBUG
260 : PRInt32 result;
261 0 : nsresult rv = aError->GetResult(&result);
262 0 : NS_ENSURE_SUCCESS(rv, rv);
263 0 : nsCAutoString message;
264 0 : rv = aError->GetMessage(message);
265 0 : NS_ENSURE_SUCCESS(rv, rv);
266 :
267 0 : nsCAutoString warnMsg;
268 0 : warnMsg.AppendLiteral("Unable to vacuum database: ");
269 0 : warnMsg.Append(mDBFilename);
270 0 : warnMsg.AppendLiteral(" - ");
271 0 : warnMsg.AppendInt(result);
272 0 : warnMsg.AppendLiteral(" ");
273 0 : warnMsg.Append(message);
274 0 : NS_WARNING(warnMsg.get());
275 : #endif
276 :
277 : #ifdef PR_LOGGING
278 : {
279 : PRInt32 result;
280 0 : nsresult rv = aError->GetResult(&result);
281 0 : NS_ENSURE_SUCCESS(rv, rv);
282 0 : nsCAutoString message;
283 0 : rv = aError->GetMessage(message);
284 0 : NS_ENSURE_SUCCESS(rv, rv);
285 0 : PR_LOG(gStorageLog, PR_LOG_ERROR,
286 : ("Vacuum failed with error: %d '%s'. Database was: '%s'",
287 : result, message.get(), mDBFilename.get()));
288 : }
289 : #endif
290 0 : return NS_OK;
291 : }
292 :
293 : NS_IMETHODIMP
294 0 : Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
295 : {
296 0 : NS_NOTREACHED("Got a resultset from a vacuum?");
297 0 : return NS_OK;
298 : }
299 :
300 : NS_IMETHODIMP
301 1 : Vacuumer::HandleCompletion(PRUint16 aReason)
302 : {
303 1 : if (aReason == REASON_FINISHED) {
304 : // Update last vacuum time.
305 1 : PRInt32 now = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
306 1 : MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
307 2 : nsCAutoString prefName(PREF_VACUUM_BRANCH);
308 1 : prefName += mDBFilename;
309 2 : DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
310 1 : MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
311 : }
312 :
313 1 : notifyCompletion(aReason == REASON_FINISHED);
314 :
315 1 : return NS_OK;
316 : }
317 :
318 : nsresult
319 1 : Vacuumer::notifyCompletion(bool aSucceeded)
320 : {
321 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
322 1 : if (os) {
323 1 : os->NotifyObservers(nsnull, OBSERVER_TOPIC_HEAVY_IO,
324 1 : OBSERVER_DATA_VACUUM_END.get());
325 : }
326 :
327 1 : nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
328 1 : NS_ENSURE_SUCCESS(rv, rv);
329 :
330 1 : return NS_OK;
331 : }
332 :
333 : } // Anonymous namespace.
334 :
335 : ////////////////////////////////////////////////////////////////////////////////
336 : //// VacuumManager
337 :
338 49 : NS_IMPL_ISUPPORTS1(
339 : VacuumManager
340 : , nsIObserver
341 : )
342 :
343 : VacuumManager *
344 : VacuumManager::gVacuumManager = nsnull;
345 :
346 : VacuumManager *
347 1 : VacuumManager::getSingleton()
348 : {
349 1 : if (gVacuumManager) {
350 0 : NS_ADDREF(gVacuumManager);
351 0 : return gVacuumManager;
352 : }
353 1 : gVacuumManager = new VacuumManager();
354 1 : if (gVacuumManager) {
355 1 : NS_ADDREF(gVacuumManager);
356 : }
357 1 : return gVacuumManager;
358 : }
359 :
360 1 : VacuumManager::VacuumManager()
361 1 : : mParticipants("vacuum-participant")
362 : {
363 0 : MOZ_ASSERT(!gVacuumManager,
364 1 : "Attempting to create two instances of the service!");
365 1 : gVacuumManager = this;
366 1 : }
367 :
368 2 : VacuumManager::~VacuumManager()
369 : {
370 : // Remove the static reference to the service. Check to make sure its us
371 : // in case somebody creates an extra instance of the service.
372 0 : MOZ_ASSERT(gVacuumManager == this,
373 1 : "Deleting a non-singleton instance of the service");
374 1 : if (gVacuumManager == this) {
375 1 : gVacuumManager = nsnull;
376 : }
377 1 : }
378 :
379 : ////////////////////////////////////////////////////////////////////////////////
380 : //// nsIObserver
381 :
382 : NS_IMETHODIMP
383 4 : VacuumManager::Observe(nsISupports *aSubject,
384 : const char *aTopic,
385 : const PRUnichar *aData)
386 : {
387 4 : if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
388 : // Try to run vacuum on all registered entries. Will stop at the first
389 : // successful one.
390 : const nsCOMArray<mozIStorageVacuumParticipant> &entries =
391 4 : mParticipants.GetEntries();
392 : // If there are more entries than what a month can contain, we could end up
393 : // skipping some, since we run daily. So we use a starting index.
394 : static const char* kPrefName = PREF_VACUUM_BRANCH "index";
395 4 : PRInt32 startIndex = Preferences::GetInt(kPrefName, 0);
396 4 : if (startIndex >= entries.Count()) {
397 2 : startIndex = 0;
398 : }
399 : PRInt32 index;
400 7 : for (index = startIndex; index < entries.Count(); ++index) {
401 12 : nsCOMPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
402 : // Only vacuum one database per day.
403 4 : if (vacuum->execute()) {
404 : break;
405 : }
406 : }
407 8 : DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
408 4 : MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
409 : }
410 :
411 4 : return NS_OK;
412 : }
413 :
414 : } // namespace storage
415 : } // namespace mozilla
|