hive icon indicating copy to clipboard operation
hive copied to clipboard

HiveError thrown twice

Open VivienDebio opened this issue 2 years ago • 1 comments

In our flutter project we want to encrypt our Hive database, and we want to know in case of decryption error, so we deactivate crashRecovery, and expect to catch one HiveError in case of problem when opening a box.

But as weird as it can be, I think a single instance of HiveError is thrown twice... and only one is "catchable".

Please see the following code main function in a standard flutter project:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Hive.init((await getApplicationDocumentsDirectory()).path);

  Box<int>? myBox;
  try {
    myBox = await Hive.openBox<int>(
      'my_box',
      // New encryption key at each run: should result in exception at 2nd run
      encryptionCipher: HiveAesCipher(Hive.generateSecureKey()),
      // Deactivate crash recovery so that we can be informed of error
      crashRecovery: false,
    );
  } catch (error) {
    print('Caught error: $error');
  }

  // Just exercise hive
  myBox?.add(42);

  runApp(const MyApp());
}

At 2nd run of the app, an error is expected, but we get the following in the console:

I/flutter (29768): Caught error: HiveError: Wrong checksum in hive file. Box may be corrupted.
E/flutter (29768): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: HiveError: Wrong checksum in hive file. Box may be corrupted.
E/flutter (29768): #0      StorageBackendVm.initialize (package:hive/src/backend/vm/storage_backend_vm.dart:98:9)
E/flutter (29768): <asynchronous suspension>
E/flutter (29768): #1      HiveImpl._openBox (package:hive/src/hive_impl.dart:111:9)
E/flutter (29768): <asynchronous suspension>
E/flutter (29768): #2      HiveImpl.openBox (package:hive/src/hive_impl.dart:142:12)
E/flutter (29768): <asynchronous suspension>
E/flutter (29768): #3      main (package:hive_demo/main.dart:11:13)
E/flutter (29768): <asynchronous suspension>

catch successfully caught one exception (see 1st line, this is expected), but another one seems to be uncaught (see lines 2-8, this is not expected).

And if removing the try-catch around the call to Hive.openBox, we get:

E/flutter (30486): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: HiveError: Wrong checksum in hive file. Box may be corrupted.
E/flutter (30486): #0      StorageBackendVm.initialize (package:hive/src/backend/vm/storage_backend_vm.dart:98:9)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #1      HiveImpl._openBox (package:hive/src/hive_impl.dart:111:9)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #2      HiveImpl.openBox (package:hive/src/hive_impl.dart:142:12)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #3      main (package:hive_demo/main.dart:11:11)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): 
E/flutter (30486): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: HiveError: Wrong checksum in hive file. Box may be corrupted.
E/flutter (30486): #0      StorageBackendVm.initialize (package:hive/src/backend/vm/storage_backend_vm.dart:98:9)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #1      HiveImpl._openBox (package:hive/src/hive_impl.dart:111:9)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #2      HiveImpl.openBox (package:hive/src/hive_impl.dart:142:12)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): #3      main (package:hive_demo/main.dart:11:11)
E/flutter (30486): <asynchronous suspension>
E/flutter (30486): 

Where it seems the exact same exception instance is uncaught twice 😬

The demo pubspec.yaml (with Flutter 3.13.8):

dependencies:
  flutter:
    sdk: flutter

  hive: ^2.2.3
  path_provider: ^2.1.1

We have investigated a bit the hive package code (v2.2.3) and we may have found the root cause in hive_impl.dart at line 116-123:

      } catch (error, stackTrace) {
        newBox?.close();
        completer.completeError(error, stackTrace);
        rethrow;
      } finally {
        // ignore: unawaited_futures
        _openingBoxes.remove(name);
      }

We think that the error is, here, kind of duplicated:

  • once with completeError (we think this one is out of scope to be caught)
  • another time with rethrow (this one is probably the one which is caught in main)

Thank you in advance for you help 😊

Edit: Further insight, we have tried to comment completer.completeError(error, stackTrace) (hive_impl.dart:118) in our local checkout of hive package, and the uncaught exception/error has disappeared. Therefore, this is probably the root cause. But simply removing this line may break some functionality elsewhere (we have no experience in the hive code).

VivienDebio avatar Oct 23 '23 13:10 VivienDebio

I encountered this issue while testing. Perhaps you can encapsulate your code with runZonedGuarded. This will catch both exceptions; at least, in my tests, this approach has worked. You would need to adjust your code as follows:

      final box = await runZonedGuarded(() async {
        return  Hive.openBox<int>(
          'my_box',
          encryptionCipher: HiveAesCipher(Hive.generateSecureKey()),
          crashRecovery: false,
        );
      }, (error, stack) async {
        print('Caught error: $error');
      });

SimonWeidemann avatar Apr 02 '24 11:04 SimonWeidemann