Recovering From SAP Mobile Server Failures

Add application code to check for and recover from from SAP Mobile Server failures.

It is highly recommended that you add a catch call to all synchronize methods (synchronize(), begingSynchronize(), and so on) within your applications to allow the application to recover if SAP Mobile Server fails and needs to be restored from an older database. If not, you may have to reinstall the application manually for all users so they can resynchronize with SAP Mobile Server.

See Restoring from an Older Backup Database File (Data Loss) in the System Administration Guide for information about SAP Mobile Server recovery.

As a best practice, and not included in these examples, application developers should include code that informs mobile application users about:
  • What is going to happen (for example, reregistering, recreating the local database, and so on). And,
  • The reason for the action (for example, lost registration, server is restored, and so on).
And prompt them for confirmation before executing the code.
  1. The server is restored to a state prior to client registration, the Application Callback class reregisters:
    1. Windows Mobile, Win32 RBS client:
      public class MyApplicationCallback : Sybase.Mobile.DefaultApplicationCallback
      { 
         bool callFlag = false; 
         public override void OnConnectionStatusChanged(int connectionStatus, 
         int errorCode, string errorMessage) 
         { 
            //Error 580 indicates that the application appears registered to the device but 
            // not in the server. If true, the application reregisters with the server
            if (errorCode == 580 && callFlag) 
            { 
               callFlag = true; 
               Thread registerThread = new Thread(new ThreadStart(reregister)); 
               registerThread.Name = "reregister";
               registerThread.Start(); 
            } 
         }  
         private void reregister() 
         {
            System.Console.WriteLine("RegisterApplication...");
            Application.GetInstance().RegisterApplication();
            System.Console.WriteLine("RegisterApplication done"); 
         } 
      }    
    2. Windows MBS/DOEC client:
      public class MyApplicationCallback : Sybase.Mobile.DefaultApplicationCallback
      { 
         bool callFlag = false; 
         public override void OnConnectionStatusChanged(int connectionStatus, 
         int er rorCode, string errorMessage) 
         { 
      //Error 580 indicates that the application appears registered to the device but 
            // not in the server. If true, the application reregisters with the server
      
            if (errorCode == 580 && !callFlag) 
            { 
               System.Console.WriteLine("OnConnectionStatusChanged: got errorCode580"); 
               callFlag = true; 
               Thread registerThread = new Thread(new ThreadStart(reregister)); 
               registerThread.Name = "reregister";
               registerThread.Start(); 
            } 
         }  
         private void reregister() 
         { 
            System.Console.WriteLine("RegisterApplication...");  
            Application APP1 = Application.GetInstance(); 
            APP1.ApplicationCallback = new MyApplicationCallback(); 
       
            ConnectionProfile cp = MyDatabase.GetSynchronizationProfile(); 
            cp.AsyncReplay = false; 
            cp.EnableTrace(false); 
            cp.Save();  
      
            MyDatabase.SetApplication(APP1); 
            Console.WriteLine("####SetApplication OK...");  
            ConnectionProperties props = APP1.ConnectionProperties; 
            props.ServerName = Host; 
            props.PortNumber = Port;  
      
            LoginCredentials login = new LoginCredentials(Username, Credential);
            props.LoginCredentials = login; 
            Console.WriteLine("####RegisterApplication...");  
      
            Application.GetInstance().RegisterApplication();  
      
            while (APP1.ConnectionStatus != ConnectionStatus.CONNECTED) 
               {
                  Thread.Sleep(1000); 
                  Console.WriteLine("Wait for connected, sleep 1 second");
               } 
               System.Console.WriteLine("RegisterApplication done"); 
            } 
         }    
  2. The server is restored to a state to which the client is registered and synchronized, but the server's state is earlier than the client's current synchronized state. When the client synchronizes (either synchronous call, synchronize(), or asynchronous call, beginSynchronize() ), the client gets an error Sybase.Persistence.SynchronizeException.SQLE_UPLOAD_FAILED_AT_SERVER. The client application then needs to catch this exception in order to recover on the client side.
    1. Windows Mobile, Win32 RBS client needs to startconnection again and subscribe:
      Note: When the Windows Mobile Win32 RBS client calls DB.Synchronize() with the callback handler, the "SQLE_UPLOAD_FAILED_AT_SERVER" error may appear in both callback's OnSychronize() method, and an exception caught in the DB.Synchronize() call. When handling the client side recovery from OnSynchronize(), be very careful when coding (threading), otherwise the exception nested synchronize call is detected because of synchronize method is invoked in onSynchronize callback may be encountered. This client side recovery example catches the exception in DB.Synchronize().
      catch (Exception ex) 
         {  
          
        if (ex is Sybase.Persistence.SynchronizeException) 
            { 
               Sybase.Persistence.SynchronizeException syncException = 
               (Sybase.Persistence.SynchronizeException)ex; 
        //If an error during sycnhronization is encountered close the connection,
      //address the exception, delete the client database, reregister/subscribe/resync
      if (syncException.ErrorCode == 
               Sybase.Persistence.SynchronizeException.SQLE_UPLOAD_FAILED_AT_SERVER) 
               {
                  TestDB.CloseConnection(); 
                  TestDB.DeleteDatabase();
                  System.Console.WriteLine("Sync again"); 
                  TestDB.RegisterCallbackHandler(_ch);
                  TestDB.Subscribe(); 
                  TestDB.Synchronize();  
               } 
            } 
         } 
          
    2. Windows Mobile/Win32 asynchronous client:
      public void Synchronize() 
      { 
         try 
            { 
               Console.WriteLine("-- asynchronize --"); 
               AsyncCallbackHandlerHARA callback = new AsyncCallbackHandlerHARA(); 
               GenericList<ISynchronizationGroup> syncGroups = new GenericList<ISynchronizationGroup>();
               syncGroups.Add(AsyncOpReplayDB.GetSynchronizationGroup("default"));
               callback.userContext = Environment.TickCount + "";
               AsyncOpReplayDB.RegisterCallbackHandler(callback);
               AsyncOpReplayDB.BeginSynchronize(syncGroups, callback.userContext);
               Console.WriteLine("BeginSyncronize"); 
               int waitCount = 0; 
               while (!callback.asyncDone()) 
               { 
                  if (waitCount++ > maxWaitTime) 
                  { 
                     break;
                  } 
                  try 
                     { 
                        Thread.Sleep(1000); 
                     } 
                     catch (Exception e) 
                        {
                           Console.WriteLine("exception=" + e.ToString()); 
                         } 
                      }   
                      if (callback.errorMessage != null) 
                         { 
                            Console.WriteLine("callback errorMessage=" + callback.errorMessage); 
                            throw new Exception(callback.errorMessage); 
                         }  
                         callback.userContext = null; 
                         Console.WriteLine("-- asynchronize done --"); 
                      }  
                      catch (Exception ex) 
                         { 
                            Console.WriteLine("Exception message is: {0}", ex.Message); 
                         } 
                      } 
                   }  
                   class AsyncCallbackHandlerHARA : DefaultCallbackHandler 
                      { 
                         private volatile bool asyncCompleted = false; 
                         private volatile bool asyncUploaded = false; 
                         public volatile String userContext = null; 
                         public volatile String errorMessage = null;  
                         public bool asyncDone() 
                         { 
                             return asyncCompleted; 
                         }  
                         public void RecoverClientDatabase() 
                            {
                               System.Console.WriteLine("RecoverClientDatabase start");
                               AsyncOpReplayDB.CloseConnection(); 
                               AsyncOpReplayDB.DeleteDatabase();
                               AsyncOpReplayDB.CreateDatabase();  
                               AsyncCallbackHandlerHARA callback = new AsyncCallbackHandlerHARA(); 
                               GenericList<ISynchronizationGroup> syncGroups = new
                               GenericList<ISynchronizationGroup>(); 
                               syncGroups.Add(AsyncOpReplayDB.GetSynchronizationGroup("default")); 
                               callback.userContext = Environment.TickCount + ""; 
                               AsyncOpReplayDB.RegisterCallbackHandler(callback);
                               AsyncOpReplayDB.BeginSynchronize(syncGroups, callback.userContext);  
                               System.Console.WriteLine("RecoverClientDatabase done"); 
                            }  
                            public override SynchronizationAction OnSynchronize (GenericList<ISynchronizationGroup> groups,
                            Sybase.Persistence.SynchronizationContext context) 
                               {
                                  Console.WriteLine("-- AsyncCallbackHandlerHARA OnSynchronize --");  
                                  Exception ex = context.Exception; 
                                  if (ex is Sybase.Persistence.SynchronizeException) 
                                  { 
                                     Sybase.Persistence.SynchronizeException syncException = 
                                     (Sybase.Persistence.SynchronizeException)ex; 
                                     if (syncException.ErrorCode == Sybase.Persistence.SynchronizeException.SQLE_UPLOAD_FAILED_AT_SERVER) 
                                     {
                                        RecoverClientDatabase(); 
                                     } 
                                  } 
                                  ... 
                  return SynchronizationAction.CONTINUE; 
               } 
          }    
  3. The following example illustrates a MBS/DOEC client where the client waits long enough before synching to allow the server to recover to a restored state. The client makes calls according to the state instead of using error handling code. The server is restored to a state where the client is registered but not subscribed. The client needs to unsubscribe and resubscribe.
    Public void sync() {
    …
    _rh = new TextResponseHandler();
    MyDatabase.RegisterCallbackHandler(_rh);
    oqe.Create();
    
    MyDatabase.SubmitPendingOperations();
    
    //at this point, server is restored to the state client registered and did not subscribe
    
    //client wake up and new operations
    if (APP.ConnectionStatus != ConnectionStatus.CONNECTED)
       {
       APP.StartConnection(100);
       Thread.Sleep(2000);
       }
    if(MyDatabase.IsSubscribed())
       {
       MyDatabase.Unsubscribe();
       // waiting for dowload data complete
       _rh.WaitForMessage("UnsubscribeFailure", "UnsubscribeSuccess");
       System.Console.WriteLine("Unsubscribe is called");
       MyDatabase.Subscribe();
       System.Console.WriteLine("Subscribe is called");
       _rh.WaitForMessage("SubscribeSuccess");
       }
    oqe = new ASADataTypesOQE();
    …
    }  
  4. The following example illustrates a MBS/DOEC client where the client waits long enough before synching to allow the server to recover to a restored state. The client makes calls according to the state, instead of using error handling code. The MBS/DOEC Client needs to subscribe if the server is restored to a state where the client is registered and subscribed.
    oqe.Create();  
    MyDatabase.SubmitPendingOperations();  
    lst = ASADataTypesOQE.FindAll(); 
    Console.WriteLine("ASADataTypesOQE has records: " + lst.Size()); 
    foreach (ASADataTypesOQE itm in lst) 
       {
          Console.WriteLine("ID: " + itm.Id + " | Name: " + itm.Ctext); 
       } 
    //at this point, server is restored to the state client registered and subscribed  
    
    //client wake up and new operations  
    if (!MyDatabase.IsSubscribed()) 
       { 
       MyDatabase.Subscribe(); 
       //waiting for dowload data complete 
       _rh.WaitForMessage("ImportSuccess"); 
       }