tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_retry_on_busy.js (5987B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /* eslint-disable mozilla/no-arbitrary-setTimeout */
      5 
      6 const { setTimeout } = ChromeUtils.importESModule(
      7  "resource://gre/modules/Timer.sys.mjs"
      8 );
      9 
     10 function getProfileFile(name) {
     11  let file = do_get_profile();
     12  file.append(name);
     13  return file;
     14 }
     15 
     16 function promiseAsyncDatabase(name, openOptions = 0) {
     17  return new Promise((resolve, reject) => {
     18    let file = getProfileFile(name);
     19    const connOptions = Ci.mozIStorageService.CONNECTION_DEFAULT;
     20    Services.storage.openAsyncDatabase(
     21      file,
     22      openOptions,
     23      connOptions,
     24      (status, connection) => {
     25        if (!Components.isSuccessCode(status)) {
     26          reject(new Error(`Failed to open database: ${status}`));
     27        } else {
     28          connection.QueryInterface(Ci.mozIStorageAsyncConnection);
     29          resolve(connection);
     30        }
     31      }
     32    );
     33  });
     34 }
     35 
     36 function promiseClone(db, readOnly = false) {
     37  return new Promise((resolve, reject) => {
     38    db.asyncClone(readOnly, (status, clone) => {
     39      if (!Components.isSuccessCode(status)) {
     40        reject(new Error(`Failed to clone connection: ${status}`));
     41      } else {
     42        clone.QueryInterface(Ci.mozIStorageAsyncConnection);
     43        resolve(clone);
     44      }
     45    });
     46  });
     47 }
     48 
     49 function promiseClose(db) {
     50  return new Promise((resolve, reject) => {
     51    db.asyncClose(status => {
     52      if (!Components.isSuccessCode(status)) {
     53        reject(new Error(`Failed to close connection: ${status}`));
     54      } else {
     55        resolve();
     56      }
     57    });
     58  });
     59 }
     60 
     61 function promiseExecuteStatement(statement) {
     62  return new Promise((resolve, reject) => {
     63    let rows = [];
     64    statement.executeAsync({
     65      handleResult(resultSet) {
     66        let row = null;
     67        do {
     68          row = resultSet.getNextRow();
     69          if (row) {
     70            rows.push(row);
     71          }
     72        } while (row);
     73      },
     74      handleError(error) {
     75        reject(new Error(`Failed to execute statement: ${error.message}`));
     76      },
     77      handleCompletion(reason) {
     78        if (reason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
     79          resolve(rows);
     80        } else {
     81          reject(new Error("Statement failed to execute or was cancelled"));
     82        }
     83      },
     84    });
     85  });
     86 }
     87 
     88 add_task(async function test_retry_on_busy() {
     89  info("Open first writer in WAL mode and set up schema");
     90  let db1 = await promiseAsyncDatabase("retry-on-busy.sqlite");
     91 
     92  let walStmt = db1.createAsyncStatement(`PRAGMA journal_mode = WAL`);
     93  await promiseExecuteStatement(walStmt);
     94  let createAStmt = db1.createAsyncStatement(`CREATE TABLE a(
     95    b INTEGER PRIMARY KEY
     96  )`);
     97  await promiseExecuteStatement(createAStmt);
     98  let createPrevAStmt = db1.createAsyncStatement(`CREATE TEMP TABLE prevA(
     99    curB INTEGER PRIMARY KEY,
    100    prevB INTEGER NOT NULL
    101  )`);
    102  await promiseExecuteStatement(createPrevAStmt);
    103  let createATriggerStmt = db1.createAsyncStatement(`
    104    CREATE TEMP TRIGGER a_afterinsert_trigger
    105    AFTER UPDATE ON a FOR EACH ROW
    106    BEGIN
    107      REPLACE INTO prevA(curB, prevB) VALUES(NEW.b, OLD.b);
    108    END`);
    109  await promiseExecuteStatement(createATriggerStmt);
    110 
    111  info("Open second writer");
    112  let db2 = await promiseClone(db1);
    113 
    114  info("Attach second writer to new database");
    115  let attachStmt = db2.createAsyncStatement(`ATTACH :path AS newDB`);
    116  attachStmt.bindByName(
    117    "path",
    118    getProfileFile("retry-on-busy-attach.sqlite").path
    119  );
    120  await promiseExecuteStatement(attachStmt);
    121 
    122  info("Create triggers on second writer");
    123  let createCStmt = db2.createAsyncStatement(`CREATE TABLE newDB.c(
    124    d INTEGER PRIMARY KEY
    125  )`);
    126  await promiseExecuteStatement(createCStmt);
    127  let createCTriggerStmt = db2.createAsyncStatement(`
    128    CREATE TEMP TRIGGER c_afterdelete_trigger
    129    AFTER DELETE ON c FOR EACH ROW
    130    BEGIN
    131      INSERT INTO a(b) VALUES(OLD.d);
    132    END`);
    133  await promiseExecuteStatement(createCTriggerStmt);
    134 
    135  info("Begin transaction on second writer");
    136  let begin2Stmt = db2.createAsyncStatement("BEGIN IMMEDIATE");
    137  await promiseExecuteStatement(begin2Stmt);
    138 
    139  info(
    140    "Begin transaction on first writer; should busy-wait until second writer is done"
    141  );
    142  let begin1Stmt = db1.createAsyncStatement("BEGIN IMMEDIATE");
    143  let promise1Began = promiseExecuteStatement(begin1Stmt);
    144  let update1Stmt = db1.createAsyncStatement(`UPDATE a SET b = 3 WHERE b = 1`);
    145  let promise1Updated = promiseExecuteStatement(update1Stmt);
    146 
    147  info("Wait 5 seconds");
    148  await new Promise(resolve => setTimeout(resolve, 5000));
    149 
    150  info("Commit transaction on second writer");
    151  let insertIntoA2Stmt = db2.createAsyncStatement(`INSERT INTO a(b) VALUES(1)`);
    152  await promiseExecuteStatement(insertIntoA2Stmt);
    153  let insertIntoC2Stmt = db2.createAsyncStatement(`INSERT INTO c(d) VALUES(2)`);
    154  await promiseExecuteStatement(insertIntoC2Stmt);
    155  let deleteFromC2Stmt = db2.createAsyncStatement(`DELETE FROM c`);
    156  await promiseExecuteStatement(deleteFromC2Stmt);
    157  let commit2Stmt = db2.createAsyncStatement("COMMIT");
    158  await promiseExecuteStatement(commit2Stmt);
    159 
    160  info("Await and commit transaction on first writer");
    161  await promise1Began;
    162  await promise1Updated;
    163 
    164  let commit1Stmt = db1.createAsyncStatement("COMMIT");
    165  await promiseExecuteStatement(commit1Stmt);
    166 
    167  info("Verify our writes succeeded");
    168  let select1Stmt = db2.createAsyncStatement("SELECT b FROM a");
    169  let rows = await promiseExecuteStatement(select1Stmt);
    170  deepEqual(
    171    rows.map(row => row.getResultByName("b")),
    172    [2, 3]
    173  );
    174 
    175  info("Clean up");
    176  for (let stmt of [
    177    walStmt,
    178    createAStmt,
    179    createPrevAStmt,
    180    createATriggerStmt,
    181    attachStmt,
    182    createCStmt,
    183    createCTriggerStmt,
    184    begin2Stmt,
    185    begin1Stmt,
    186    insertIntoA2Stmt,
    187    insertIntoC2Stmt,
    188    deleteFromC2Stmt,
    189 
    190    commit2Stmt,
    191    update1Stmt,
    192    commit1Stmt,
    193    select1Stmt,
    194  ]) {
    195    stmt.finalize();
    196  }
    197  await promiseClose(db1);
    198  await promiseClose(db2);
    199 });