Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IndexedDb: database.close() is hanging

Tags:

dart

indexeddb

I'm writing integration tests for some data storage stuff that I'm using IndexedDb for. This involves setting up each test by creating a database, performing some operation (running the test), and then tearing down each test by calling database.close() and then deleting the database by calling window.indexedDB.deleteDatabase(DB_NAME).

The documentation for IDBDatabase.close() states that "The close() method of the IDBDatabase interface returns immediately and closes the connection in a separate thread. Close doesn't accept a callback to be fired once the database has actually been closed, so there's no way to determine with certainty that the connection has been closed.

My initial test was timing out when attempting to delete the database with window.indexedDB.deleteDatabase(DB_NAME). All the test was doing was opening the database, no operations beside that were performed. I was able to fix this by adding a small timeout after calling database.close().

After adding another test that adds data to the database, the call to delete the database is hanging again, even with the timeout. The data does get successfully added and the transaction callback completes, so I'm not sure what why the call to database.close() would be hanging. Any insight would be greatly appreciated.

EDIT

I created a project that illustrates this problem. The code can be found here: https://github.com/bgourlie/idb-hang-repro

A few things to note -- The repro is written in dart, since that's where I'm seeing this problem. The behavior is reproduced both in Chrome and Dartium (a special version of Chromium with the dart VM embedded). For those who have not used Dart but would still like to mess with this issue, follow these steps:

  • Download and install Dart. It is also available to install via homebrew.
  • Once you extract the dart install, add {extracted_dir}/dart/dart-sdk/bin to your PATH.
  • git clone https://github.com/bgourlie/idb-hang-repro.git
  • cd idb-hang-repro
  • pub get
  • pub serve

This will start the pub development server, most likely, at http://localhost:8080. I have reproduced the issue within the test runner, which would be accessed at http://localhost:8080/tests.html. It will take a short time for the test to timeout and any output to be displayed. There are also important print messages that will be shown on the developer console.

like image 264
w.brian Avatar asked Jan 10 '23 03:01

w.brian


1 Answers

As I have been doing tons of Indexed db experiment for my idb_shim project, I can share that I was able to write unit test on a fresh database as long as I ensured that

  • the last transaction was completed before closing the database
  • the database was closed before deleting it

According to that I was able to fix your test project (thanks for sharing it), with the following changes:

  • In your first test, ensure that you return the future from tx.completed so that tearDown is not called before the transaction is completed (no need to add completer, people tends to forget that you can safely return a future in a test/setUp/tearDown and that the unit test framework will 'wait' for them):
return tx.completed.then((_) {
    print('transaction complete.');
},...
  • In your tearDown, call db.close before deleting it.

As a side note, I typically prefer to delete the dabase in the setUp fonction so that it works fine if a previous test failed with the database not cleaned up. so the solution for your existing code is then:

setUp(() {
  return dom.window.indexedDB.deleteDatabase(DB_NAME, onBlocked: (e) {
    print('delete db blocked, but completing future anyway');
  }).then((_) {
    print('db successfully deleted!');
    return dom.window.indexedDB.open(DB_NAME, version: 1, onUpgradeNeeded: (VersionChangeEvent e) {
      print('db upgrade called (${e.oldVersion} -> ${e.newVersion})');
      final db = (e.target as Request).result;
      db.createObjectStore('foo', autoIncrement: true);
    }, onBlocked: (e) => print('open blocked.')).then((_db_) {
      print('db opened.');
      db = _db_;
    });
  });
});

tearDown(() {
  // note the 'close' here
  db.close();
});

group('indexed DB delete hang repro', () {
  test('second test which will add data', () {
    print('adding data in second test...');
    final tx = db.transaction('foo', 'readwrite');
    final objectStore = tx.objectStore('foo');
    objectStore.add({
      'bar': 1,
      'baz': 2
    }).then((addedKey) {
      print('object added to store with key=$addedKey');
    }, onError: (e) => print('error adding object!'));

    // note the 'return' here
    return tx.completed.then((_) {
      print('transaction complete.');
    }, onError: (e) => print('transaction errored!'));
  });

  test('call setup and teardown', () {
    print('just setup and teardown being called in first test.');
  });
});
like image 186
alextk Avatar answered Jan 11 '23 19:01

alextk