Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/storage/VacuumManager.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
32
0
#define OBSERVER_DATA_VACUUM_BEGIN u"vacuum-begin"
33
0
#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
0
#define PREF_VACUUM_BRANCH "storage.vacuum.last."
38
39
// Time between subsequent vacuum calls for a certain database.
40
0
#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
BaseCallback::HandleError(mozIStorageError *aError)
64
0
{
65
#ifdef DEBUG
66
  int32_t result;
67
  nsresult rv = aError->GetResult(&result);
68
  NS_ENSURE_SUCCESS(rv, rv);
69
  nsAutoCString message;
70
  rv = aError->GetMessage(message);
71
  NS_ENSURE_SUCCESS(rv, rv);
72
73
  nsAutoCString warnMsg;
74
  warnMsg.AppendLiteral("An error occured during async execution: ");
75
  warnMsg.AppendInt(result);
76
  warnMsg.Append(' ');
77
  warnMsg.Append(message);
78
  NS_WARNING(warnMsg.get());
79
#endif
80
  return NS_OK;
81
0
}
82
83
NS_IMETHODIMP
84
BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
85
0
{
86
0
  // We could get results from PRAGMA statements, but we don't mind them.
87
0
  return NS_OK;
88
0
}
89
90
NS_IMETHODIMP
91
BaseCallback::HandleCompletion(uint16_t aReason)
92
0
{
93
0
  // By default BaseCallback will just be silent on completion.
94
0
  return NS_OK;
95
0
}
96
97
NS_IMPL_ISUPPORTS(
98
  BaseCallback
99
, mozIStorageStatementCallback
100
)
101
102
////////////////////////////////////////////////////////////////////////////////
103
//// Vacuumer declaration.
104
105
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
Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant)
125
  : mParticipant(aParticipant)
126
0
{
127
0
}
128
129
bool
130
Vacuumer::execute()
131
0
{
132
0
  MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
133
0
134
0
  // 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
0
  }
142
0
143
0
  // Ask for the expected page size.  Vacuum can change the page size, unless
144
0
  // the database is using WAL journaling.
145
0
  // 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
0
  }
153
0
154
0
  // Get the database filename.  Last vacuum time is stored under this name
155
0
  // 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
0
  }
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
0
168
0
  // Check interval from last vacuum.
169
0
  int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
170
0
  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
0
    // This database was vacuumed recently, skip it.
176
0
    return false;
177
0
  }
178
0
179
0
  // Notify that we are about to start vacuuming.  The participant can opt-out
180
0
  // if it cannot handle a vacuum at this time, and then we'll move to the next
181
0
  // 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
0
  }
188
0
189
0
  // Notify a heavy IO task is about to start.
190
0
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
191
0
  if (os) {
192
0
    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
0
  }
197
0
198
0
  // Execute the statements separately, since the pragma may conflict with the
199
0
  // vacuum, if they are executed in the same transaction.
200
0
  nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
201
0
  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
0
212
0
  nsCOMPtr<mozIStorageAsyncStatement> stmt;
213
0
  rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
214
0
    "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
0
220
0
  return true;
221
0
}
222
223
////////////////////////////////////////////////////////////////////////////////
224
//// mozIStorageStatementCallback
225
226
NS_IMETHODIMP
227
Vacuumer::HandleError(mozIStorageError *aError)
228
0
{
229
0
  int32_t result;
230
0
  nsresult rv;
231
0
  nsAutoCString message;
232
0
233
#ifdef DEBUG
234
  rv = aError->GetResult(&result);
235
  NS_ENSURE_SUCCESS(rv, rv);
236
  rv = aError->GetMessage(message);
237
  NS_ENSURE_SUCCESS(rv, rv);
238
239
  nsAutoCString warnMsg;
240
  warnMsg.AppendLiteral("Unable to vacuum database: ");
241
  warnMsg.Append(mDBFilename);
242
  warnMsg.AppendLiteral(" - ");
243
  warnMsg.AppendInt(result);
244
  warnMsg.Append(' ');
245
  warnMsg.Append(message);
246
  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
0
           ("Vacuum failed with error: %d '%s'. Database was: '%s'",
256
0
            result, message.get(), mDBFilename.get()));
257
0
  }
258
0
  return NS_OK;
259
0
}
260
261
NS_IMETHODIMP
262
Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
263
0
{
264
0
  MOZ_ASSERT_UNREACHABLE("Got a resultset from a vacuum?");
265
0
  return NS_OK;
266
0
}
267
268
NS_IMETHODIMP
269
Vacuumer::HandleCompletion(uint16_t aReason)
270
0
{
271
0
  if (aReason == REASON_FINISHED) {
272
0
    // 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
0
  }
280
0
281
0
  notifyCompletion(aReason == REASON_FINISHED);
282
0
283
0
  return NS_OK;
284
0
}
285
286
nsresult
287
Vacuumer::notifyCompletion(bool aSucceeded)
288
0
{
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
0
  }
294
0
295
0
  nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
296
0
  NS_ENSURE_SUCCESS(rv, rv);
297
0
298
0
  return NS_OK;
299
0
}
300
301
} // namespace
302
303
////////////////////////////////////////////////////////////////////////////////
304
//// VacuumManager
305
306
NS_IMPL_ISUPPORTS(
307
  VacuumManager
308
, nsIObserver
309
)
310
311
VacuumManager *
312
VacuumManager::gVacuumManager = nullptr;
313
314
already_AddRefed<VacuumManager>
315
VacuumManager::getSingleton()
316
0
{
317
0
  //Don't allocate it in the child Process.
318
0
  if (!XRE_IsParentProcess()) {
319
0
    return nullptr;
320
0
  }
321
0
322
0
  if (!gVacuumManager) {
323
0
    auto manager = MakeRefPtr<VacuumManager>();
324
0
    MOZ_ASSERT(gVacuumManager == manager.get());
325
0
    return manager.forget();
326
0
  }
327
0
  return do_AddRef(gVacuumManager);
328
0
}
329
330
VacuumManager::VacuumManager()
331
  : mParticipants("vacuum-participant")
332
0
{
333
0
  MOZ_ASSERT(!gVacuumManager,
334
0
             "Attempting to create two instances of the service!");
335
0
  gVacuumManager = this;
336
0
}
337
338
VacuumManager::~VacuumManager()
339
0
{
340
0
  // Remove the static reference to the service.  Check to make sure its us
341
0
  // in case somebody creates an extra instance of the service.
342
0
  MOZ_ASSERT(gVacuumManager == this,
343
0
             "Deleting a non-singleton instance of the service");
344
0
  if (gVacuumManager == this) {
345
0
    gVacuumManager = nullptr;
346
0
  }
347
0
}
348
349
////////////////////////////////////////////////////////////////////////////////
350
//// nsIObserver
351
352
NS_IMETHODIMP
353
VacuumManager::Observe(nsISupports *aSubject,
354
                       const char *aTopic,
355
                       const char16_t *aData)
356
0
{
357
0
  if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
358
0
    // Try to run vacuum on all registered entries.  Will stop at the first
359
0
    // successful one.
360
0
    nsCOMArray<mozIStorageVacuumParticipant> entries;
361
0
    mParticipants.GetEntries(entries);
362
0
    // If there are more entries than what a month can contain, we could end up
363
0
    // skipping some, since we run daily.  So we use a starting index.
364
0
    static const char* kPrefName = PREF_VACUUM_BRANCH "index";
365
0
    int32_t startIndex = Preferences::GetInt(kPrefName, 0);
366
0
    if (startIndex >= entries.Count()) {
367
0
      startIndex = 0;
368
0
    }
369
0
    int32_t index;
370
0
    for (index = startIndex; index < entries.Count(); ++index) {
371
0
      RefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
372
0
      // Only vacuum one database per day.
373
0
      if (vacuum->execute()) {
374
0
        break;
375
0
      }
376
0
    }
377
0
    DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
378
0
    MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
379
0
  }
380
0
381
0
  return NS_OK;
382
0
}
383
384
} // namespace storage
385
} // namespace mozilla