How to simulate client reset request?

Hello!

I have implemented client reset functionality, but I have not any idea how to simulate a client reset request to test the functionality. So could you guide to me how can I easily do that?

Thank you.

1 Like

Hi @Vardan_Sargsyan92,

you can force a client reset by changing the schema in Atlas. Note that you have to stop Sync to do this and restart it afterwards.
Doing this will send a client reset request to the client where you can handle it with your client reset functionality.

Our documentation shows an example on how to handle this request while the app is running and what to do if the client reset fails (which essentially means restarting the app and by that re-downloading the Realm).

Let me know if you have any other questions. :slight_smile:

Best,
Dominic

1 Like

HI @Dominic_Frei. Thank you for your response. I have followed the example that you have mentioned above and I do

  1. realm.Dispose();
  2. clientResetException.InitiateClientReset();

when a client reset exception emits. Also, I tried to simulate the client reset by

  1. making schema change (I have added a new property inside the realm object)
  2. making a destructive change (I have changed already existing object’s property type)

None of these cases emerged a client reset request, and I guess I do something wrong.

Before the schema change, I paused the sync, then made the change, then resume realm-sync but nothing happened.

1 Like

Hi @Vardan_Sargsyan92,

would you mind sharing your code so we can look at it together?

Is the client reset functionality added to the session error handler?
What happens when you restart? Does the change actually appear?
Is data reset?

All this would indicate to me that the client reset request reaches your app but I’m just speculating.
Looking at the code might give us more clues.

1 Like

Hi @Dominic_Frei

Sure. I have the following classes:

 public interface IRealmSyncSessionExceptionHandler
    {
        void StartMonitoringSessionExceptions();
        void StopMonitoringSessionExceptions();
    }

The App.Xaml.cs inherits IRealmSyncSessionExceptionHandler

 public partial class App : IRealmSyncSessionExceptionHandler
    {
        private IDisposable _sessionOnErrorDisposable;

        public void StartMonitoringSessionExceptions()
        {
            var schedulerProvider = Container.Resolve<ISchedulerProvider>();

            IObservable<EventPattern<ErrorEventArgs>> sessionOnErrorObservable = Observable.FromEventPattern<ErrorEventArgs>(handler => Session.Error += handler,
                                                                                                                             handler => Session.Error -= handler);

            _sessionOnErrorDisposable = sessionOnErrorObservable
                                        .SubscribeOn(schedulerProvider.ThreadPool)
                                        .ObserveOn(schedulerProvider.CurrentSynchronizationContext)
                                        .SubscribeAsync(async x => await SessionOnErrorAsync(x.Sender, x.EventArgs));
        }

        public void StopMonitoringSessionExceptions()
        {
            _sessionOnErrorDisposable.Dispose();
        }

        private async Task SessionOnErrorAsync(object sender, ErrorEventArgs e)
        {
            var session = (Session)sender;

            _logger.Error(e.Exception, "Realm session raised an exception for user {UserId} with database {DatabasePath}", session.Path, session.User.Id);

            if(e.Exception is ClientResetException clientResetException)
            {
                await OnClientResetRequested(session, clientResetException);
            }
        }

        private async Task OnClientResetRequested(Session session, ClientResetException clientResetException)
        {
            _logger.Error("Client ResetAsync requested for " + session.Path + "due to " + clientResetException.Message);

            var pageDialogService = Container.Resolve<IPageDialogService>();

            // Prompt user to perform client reset immediately. If they don't do it,
            // 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.
            bool shouldConfirmReset = await pageDialogService.DisplayAlertAsync(CommonStrings.CloudSyncTerminatedDialogTitle,
                                                                                CommonStrings.ClientResetRequestedMessage,
                                                                                CommonStrings.ResetNow,
                                                                                CommonStrings.ResetLater);

            if(shouldConfirmReset)
            {
                bool didReset = await Container.Resolve<IClientResetService>().ResetAsync(clientResetException);

                if(didReset)
                {
                    // Navigate the user back to the main page or reopen the
                    // the Realm and reinitialize the current page.
                    Restart();
                }
                else
                {
                    // ResetAsync failed - notify user that they'll need to restart the app.
                    await pageDialogService.DisplayAlertAsync(CommonStrings.Warning,
                                                              CommonStrings.ClientResetFailedMessage,
                                                              CommonStrings.Ok);
                }
            }
        }
    }

Here is the client reset interface and its implementation:

 public interface IClientResetService
    {
        /// <summary>
        ///     Initiates the client reset process.
        /// </summary>
        /// <returns><c>true</c> if actions were run successfully, <c>false</c> otherwise.</returns>
        /// <remarks>
        ///     On Windows, all Realm instances for that path must be disposed before this method is called or an
        ///     Exception will be thrown.
        /// </remarks>
        public Task<bool> ResetAsync(ClientResetException clientResetException, CancellationToken token = default);
    }
internal class ClientResetService : IClientResetService
    {
        private readonly IRealmFactory _realmFactory;
        private readonly IDatabaseManager _databaseManager;

        public ClientResetService(IRealmFactory realmFactory,
                                  IDatabaseManager databaseManager)
        {
            _databaseManager = databaseManager;
            _realmFactory = realmFactory;
        }

        public async Task<bool> ResetAsync(ClientResetException clientResetException, CancellationToken token = default)
        {
            Realm realm = await _realmFactory.GetRealmAsync(_databaseManager.CurrentDatabase);

            // Close the Realm before doing the reset as it'll need
            // to be deleted and all objects obtained from it will be
            // invalidated.
            realm.Dispose();

            return clientResetException.InitiateClientReset();
            
        }
    }

Thank you for sharing the code. Nothing caught my idea immediately but let me understand something else a bit better first:

  • Is the problem that the error is not received at all? Or just not handled as expected?
  • Does the reset happen when you restart the app (and only the manual way while the app is still running is the problem)? ← Trying to figure out if that is maybe not connected to above code at all.
1 Like

@Dominic_Frei

Related to the first point.

Actually, I did not have a chance to understand how the reset works or even it works or not because I am still not able to reproduce a client reset exception.

Second I haven’t completely understood what should I do on my side (The client-side) when the client reset requests. Does the clientResetException.InitiateClientReset() is enough or should I remove the current database and reinstall it again (download the database from realm cloud).

@Dominic_Frei

  1. The problem is whatever I do, ClientResetException is not raised on the client application. Here are more details about the steps I take
    1. I start the app on UWP and subscribe to session errors
    2. While the app is running, I go and pause the sync
    3. I go, and perform destructive schema change. For instance, I remove an existing property from one of the models
    4. I save the schema and resume the sync
      Is there something obviously wrong with these steps?
  2. I do receive the client reset exception after the app is restarted. This behavior raises a lot of questions. How should we handle client reset if the app is running then?

@Dominic_Frei

One more thing that I have noticed during testing.
When I receive the client reset exception (when re-launching the application), I do reset and restart the application (dispose DI container and call App.OnInitialized()), the client reset exception rises again which, causes an application crash. And when I re-launch the application, the exception does not rise.

This is a great step forward in figuring out what’s wrong. Since you receive the client reset after restart my expectation would be that the changes you apply in Atlas lead to the correct result. Which then leads me to think that the problem lies within catching the exception.

But when you say I do receive the client reset exception after the app is restarted. you’re referring to the very same code right? So, if I understand correctly, the error doesn’t fire until you restart the app?

1 Like

@Dominic_Frei

Yes, I am referring to the same code (almost the same) and, yes, You are correct. The error doesn’t fire until I restart (re-launch) the app. I assume that behavior is not expected and, the app should receive real-time client reset exception.

Some details which discovered during the investigation.

  1. Client reset exception does not throw on real-time.
  2. When I do client reset and restart the application-UI again (please do not confuse with re-launching an app that is closing and re-running the application) the client reset exception throws again.

I’ve had a chat with @nirinchev about that. He explained that pausing the Sync will lead to a 503 which leads to the app trying to reconnect after a while. Maybe you closed the app right before the next successful reconnect (which would only happen after Sync was resumed) happened?
What happens if you keep the app open for a little longer after resuming Sync?

@Dominic_Frei

  1. I run the application.
  2. I go to the realm atlas and pause the sync.
  3. In the application, an exception rises, with a message “End of input” (ErrorCode = 1)
  4. The second exception rises, with the message “Premature end of input” (ErrorCode = 2)
  5. I go to the realm atlas and make a destructive change and tap on the button with the text “Save Changes & Reinitialize Sync”
  6. I navigate back to sync and Enable Sync
  7. I am waiting 20 minutes but no exception rises
  8. I Re-launch the application and get a “DivergingHistories” error with the message “Bad client file identifier (IDENT) - request logs URL: https://realm.mongodb.com/groups/614c3e0998bbb0088773a923/apps/614c52b5b23f417e5ed4c2aa/logs?co_id=614f00593a412e3a60595e94”.
  9. I do client reset and restart the application navigation (initialize the DI container and create a new instance of the realm)
  10. I did not receive the second client-reset exception today (but I have not to change the code)

Thanks for those details!

Those exception, do you receive them during your client reset handling or somewhere else? How do you handle them?

If I understand correctly, (7) does not lead to an exception but also does not lead to a client reset correct?

btw: This link is for your account. I can’t access your data. :slight_smile:

@Dominic_Frei

  1. I receive those exceptions when I re-launch the application inside the event handler.
  2. Yes, you are correct. I create/update/delete the realm objects inside the application, but nothing leads to client reset until I re-launch the application.
  3. That link refers to my realm cluster logs. You should not be able to see that )))

@Vardan_Sargsyan92

Thank you for the update. I can’t see anything wrong with that straight away.

Can you give https://docs.mongodb.com/realm/sdk/dotnet/advanced-guides/client-reset/ a try? And use the code and explanation right from the documentation? As I see it right now that should be the best option to figure out where in that documentation we might have missed out on something that you just discovered.

Thanks!

@Dominic_Frei

The sad thing is that I have already followed that sample and currently have an issue related to the not throwing client reset exception. Thanks for your response. I will continue my investigations.