IndexedDb: database.close() is hanging IndexedDb: database.close() is hanging dart dart

IndexedDb: database.close() is hanging


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.');  });});


As you noticed, there is no way to know when database.close() finishes, and window.indexedDB.deleteDatabase behaves a bit strangely when trying to delete a database is still open. But those are not insurmountable problems. Take a look at what actually happens when you try to delete a database. My reading of this says that if you try to delete a database that is still open, it will fire a versionchange event at each open database connection. Then, if the database is still open, it will fire a blocked event rather than success for your deleteDatabase, but it will still proceed and delete the database either way.

Therefore, I do something like this:

var request = indexedDB.deleteDatabase("whatever");request.onsuccess = function () {    success();};request.onfailure = function (event) {    fail(event);};request.onblocked = function () {    success();};

A better solution might be to somehow listen for the aforementioned versionchange event and close the database there, but I couldn't figure out how to do that. And that would only matter if you care if the database is open or not when you delete it, which you probably don't.