summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/test/unit/test_connection_idle_maintenance_stop.js
blob: 5453e4dc4e9a7dffa202143a6d403e5499d07109 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

/* exported testSteps */
async function testSteps() {
  // A constant used to deal with small decrease in usage when transactions are
  // transferred from the WAL file back into the original database, also called
  // as checkpointing.
  const cosmologicalConstant = 35000;

  // The maximum number of threads that can be used for database activity at a
  // single time.
  const maxConnectionThreadCount = 20;

  // The length of time that database connections will be held open after all
  // transactions have completed before doing idle maintenance.
  const connectionIdleMaintenanceMS = 2 * 1000;

  const name = "test_connection_idle_maintenance_stop";
  const abc = "abcdefghijklmnopqrstuvwxyz";

  // IndexedDB on Android does `PRAGMA auto_vacuum = FULL`, so the freelist
  // pages are moved to the end of the database file and the database file is
  // truncated to remove the freelist pages at every transaction commit.
  if (mozinfo.os == "android") {
    info("Test disabled on Android for now");
    return;
  }

  info("Setting pref");

  Services.prefs.setIntPref(
    "dom.indexedDB.connectionIdleMaintenance.pauseOnConnectionThreadMs",
    2 * connectionIdleMaintenanceMS
  );

  info("Forcing only one connection thread to be available");

  let done = false;

  // Create databases which will continuously keep their connection threads
  // busy.
  const completePromises = await (async function () {
    let promises = [];

    for (let index = 0; index < maxConnectionThreadCount - 1; index++) {
      const request = indexedDB.open(name + "-" + index, 1);

      {
        const event = await expectingUpgrade(request);

        const database = event.target.result;

        const objectStore = database.createObjectStore(name);

        objectStore.add("foo", 42);
      }

      const event = await expectingSuccess(request);

      const database = event.target.result;

      const transaction = database.transaction(name);

      const objectStore = transaction.objectStore(name);

      function doWork() {
        const request = objectStore.get(42);
        request.onsuccess = function () {
          if (!done) {
            doWork();
          }
        };
      }

      doWork();

      const promise = new Promise(function (resolve) {
        transaction.oncomplete = resolve;
      });

      promises.push(promise);
    }

    return promises;
  })();

  info("Creating database A");

  // Create a database which will be used for stopping of the connection idle
  // maintenance.
  const databaseA = await (async function () {
    const request = indexedDB.open(name + "-a", 1);

    {
      const event = await expectingUpgrade(request);

      const database = event.target.result;

      database.createObjectStore(name);
    }

    const event = await expectingSuccess(request);

    const database = event.target.result;

    return database;
  })();

  info("Creating database B");

  // Create a database for checking of the connection idle maintenance.
  {
    const request = indexedDB.open(name + "-b", 1);

    const event = await expectingUpgrade(request);

    const database = event.target.result;

    const objectStore = database.createObjectStore(name);

    // Add lots of data...
    for (let index = 0; index < 10000; index++) {
      objectStore.add(abc, index);
    }

    // And then clear it so that maintenance has some space to reclaim.
    objectStore.clear();

    await expectingSuccess(request);
  }

  info("Getting database usage before maintenance");

  const databaseUsageBeforeMaintenance = await new Promise(function (resolve) {
    getCurrentUsage(function (request) {
      resolve(request.result.databaseUsage);
    });
  });

  info("Waiting for maintenance to start");

  // This time is a double of connectionIdleMaintenanceMS which should be
  // pessimistic enough to work with randomly slowed down threads in the
  // chaos mode.
  await new Promise(function (resolve) {
    do_timeout(2 * connectionIdleMaintenanceMS, resolve);
  });

  info("Activating database A");

  // Activate an open database which should trigger stopping of the connection
  // idle maintenance of the database which had a lot of data.
  {
    const transaction = databaseA.transaction(name);

    const objectStore = transaction.objectStore(name);

    const request = objectStore.get(42);

    await requestSucceeded(request);
  }

  info("Waiting for maintenance to finish");

  // This time is a double of connectionIdleMaintenanceMS which should be
  // pessimistic enough to work with randomly slowed down threads in the
  // chaos mode.
  await new Promise(function (resolve) {
    do_timeout(2 * connectionIdleMaintenanceMS, resolve);
  });

  info("Getting database usage after maintenance");

  const databaseUsageAfterMaintenance = await new Promise(function (resolve) {
    getCurrentUsage(function (request) {
      resolve(request.result.databaseUsage);
    });
  });

  info(
    "Database usage before: " +
      databaseUsageBeforeMaintenance +
      ". " +
      "Database usage after: " +
      databaseUsageAfterMaintenance
  );

  // Checkpointing done immediately after the maintenance can slightly decrease
  // the usage even when the maintenance was stopped very early.
  ok(
    databaseUsageBeforeMaintenance - databaseUsageAfterMaintenance <
      cosmologicalConstant,
    "Maintenance did not significantly decrease database usage"
  );

  done = true;

  info("Waiting for transactions to complete");

  await Promise.all(completePromises);
}