realm / realm-dart

Realm is a mobile database: a replacement for SQLite & ORMs.
Apache License 2.0
758 stars 86 forks source link

`RealmException: No such table exists. Error code: 3020.` during client reset #1740

Open SPodjasek opened 3 months ago

SPodjasek commented 3 months ago

What happened?

We've added three new collections to sync schema and during client reset following exceptions were thrown on internal testing pre-production devices:

Android: ClientResetError message: A fatal error occurred during client reset: 'User-provided callback failed', inner error: 'RealmException: No such table exists. Error code: 3020.' iOS: ClientResetError message: A fatal error occurred during client reset: 'User-provided callback failed', inner error: 'RealmException: Error getting property count. No such table exists. Error code: 3020.'

Application hangs with 'gray-screen' and no information is displayed to the user, but after restart it starts without any issues.

Repro steps

I'm still unable to reproduce this issue. It never occurred during development, only on pre-production devices in internal testing & test flight.

Version

3.1.0

What Atlas Services are you using?

Atlas Device Sync

What type of application is this?

Flutter Application

Client OS and version

Android 14, iOS 17.5.1

Code snippets

No response

Stacktrace of the exception/crash you're getting

[Android]
          Non-fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: ClientResetError message: A fatal error occurred during client reset: 'User-provided callback failed', inner error: 'RealmException: No such table exists. Error code: 3020.'
       at RealmServices._initialize.<fn>(realm_services.dart:425)
       at ._syncErrorHandlerCallback(config_handle.dart:222)
       at ._FfiCallback_syncErrorHandlerCallback(dart:ffi)
       at RealmLibrary._realm_dart_delete_persistent_handle.#ffiClosure146(realm_bindings.dart)
       at RealmLibrary.realm_scheduler_perform_work(realm_bindings.dart:8712)
       at SchedulerHandle.invoke(scheduler_handle.dart:26)
       at Scheduler._handle(scheduler.dart:45)

[iOS]
          Non-fatal Exception: FlutterError
0  ???                            0x0 RealmServices._initialize.<fn> + 425 (realm_services.dart:425)
1  ???                            0x0 (null)._syncErrorHandlerCallback + 222 (config_handle.dart:222)
2  ???                            0x0 (null)._FfiCallback_syncErrorHandlerCallback (dart:ffi)
3  ???                            0x0 RealmLibrary._realm_dart_delete_persistent_handle.#ffiClosure146 (realm_bindings.dart)
4  ???                            0x0 RealmLibrary.realm_scheduler_perform_work + 8712 (realm_bindings.dart:8712)
5  ???                            0x0 SchedulerHandle.invoke + 26 (scheduler_handle.dart:26)
6  ???                            0x0 Scheduler._handle + 45 (scheduler.dart:45)

Relevant log output

No response

sync-by-unito[bot] commented 3 months ago

➤ PM Bot commented:

Jira ticket: RDART-1066

nielsenko commented 3 months ago

@SPodjasek Can you share the code for your manual reset fallback?

at RealmServices._initialize.(realm_services.dart:425)

SPodjasek commented 2 months ago

Code below was at version that had issues....

Line 425 is marked by comment.

        onManualResetFallback: (ClientResetError clientResetError) {
          Logger.log(
            'Manual reset fallback',
            name: 'RealmServices',
            error: jsonEncode({
              'message': clientResetError.message,
              'code': {
                'index': clientResetError.code.index,
                'name': clientResetError.code.name,
              },
            }),
          );

          CrashReporting.crashlytics
              .recordError(clientResetError, StackTrace.current, fatal: false);

          // Prompt user to perform a client reset immediately. If they don't,
          // they won't receive any data from the server until they restart the app
          // and all changes they make will be discarded when the app restarts.
          var didUserConfirmReset = true; //showUserAConfirmationDialog();
          if (didUserConfirmReset) {
            // You must close the Realm before attempting the client reset.
            _close();
            // Attempt the client reset.
            Logger.log('Attempting client reset', name: 'RealmServices');
            try {
              clientResetError.resetRealm();    // <------------------------------------------ line 425
              // Navigate the user back to the main page or reopen the
              // the Realm and reinitialize the current page.
              Logger.log('Client reset successful', name: 'RealmServices');
              _initialize(user, encryptionKey);
            } catch (err, stack) {
              // Reset failed.
              // Notify user that they'll need to update the app
              Logger.log(
                'Client reset failed',
                name: 'RealmServices',
                error: err,
              );

              CrashReporting.crashlytics.recordError(err, stack, fatal: true);

              fatal = true;
              notifyListeners();
            }
          }
SPodjasek commented 2 months ago

@nielsenko Today I've received another error of this kind in newer version, but this time stack trace points to line with just:

_realm = Realm(config);

Config here is FlexibleSyncConfiguration instance with encryption and client reset handler set to RecoverOrDiscardUnsyncedChangesHandler.

Stack trace ``` Non-fatal Exception: FlutterError 0 ??? 0x0 _raiseLastError. + 59 (error_handling.dart:59) 1 ??? 0x0 (null).using + 124 (arena.dart:124) 2 ??? 0x0 (null)._raiseLastError + 48 (error_handling.dart:48) 3 ??? 0x0 BoolEx.raiseLastErrorIfFalse + 25 (error_handling.dart:25) 4 ??? 0x0 RealmHandle._getPropertiesMetadata + 460 (realm_handle.dart:460) 5 ??? 0x0 RealmHandle.getObjectMetadata. + 454 (realm_handle.dart:454) 6 ??? 0x0 (null).using + 124 (arena.dart:124) 7 ??? 0x0 RealmHandle.getObjectMetadata + 449 (realm_handle.dart:449) 8 ??? 0x0 Realm._populateMetadata. + 212 (realm_class.dart:212) 9 ??? 0x0 MappedIterator.moveNext (dart:_internal) 10 ??? 0x0 new RealmMetadata._ + 874 (realm_class.dart:874) 11 ??? 0x0 Realm._populateMetadata + 212 (realm_class.dart:212) 12 ??? 0x0 new Realm._ + 144 (realm_class.dart:144) 13 ??? 0x0 (null).new Realm + 141 (realm_class.dart:141) 14 ??? 0x0 RealmServices._initialize + 536 (realm_services.dart:536) 15 ??? 0x0 RealmServices._recovery + 789 (realm_services.dart:789) ```

Looking through code this time it seems that client reset failed, manual reset also failed, after that application tries to remove whole synced realm directory and tries to configure it from scratch.

IsuruCTS commented 2 months ago

In my case, the problem is embeddedObject schema that I defined in Configuration.flexibleSync is not used in the code.

SPodjasek commented 2 months ago

During our update we've added two embeddedObjects, but both were used in newly added model.

nirinchev commented 2 months ago

I was able to reproduce the issue where an embedded object is added without a parent.

SPodjasek commented 2 months ago

That's interesting because on client-side it was a single commit structured like below (some lines removed):

@RealmModel(ObjectType.embeddedObject)
class _ProfileActivationInput {
  String? label;
  String? type;
}

@RealmModel(ObjectType.embeddedObject)
class _ProfileActivationVerification {
  String? comparison;
  String? error;
  String? result;
  DateTime? ts;
}

@RealmModel()
@MapTo('profile_activation')
class _ProfileActivation {
  @PrimaryKey()
  @MapTo('_id')
  ObjectId? id;

  DateTime? createdAt;
  String? employeeId;
  int? failureCount;
  late List<_ProfileActivationInput> inputs;
  // [...]
  late List<String> values;
  late List<_ProfileActivationVerification> verifications;
}

And all models were added to flexible sync at same commit:


  static final List<SchemaObject> allSchemas = [
    // [...]
    ProfileActivationInput.schema,
    ProfileActivationVerification.schema,
    ProfileActivation.schema,
  ];
nirinchev commented 2 months ago

That code should be fine - it's possible you're hitting a different issue or some non-obvious manifestation of the existing one.

lkoehl commented 2 months ago

My code looks similar to @SPodjasek code and results in the same error. Any ideas for a workaround?

nirinchev commented 2 months ago

If you have an easy to run repro case, I should be able to track down what's wrong. Without a clear understanding of the root cause, it's not obvious to me what a workaround would look like.