5252import java .util .Objects ;
5353import java .util .concurrent .CountDownLatch ;
5454import java .util .concurrent .TimeUnit ;
55+ import java .util .concurrent .atomic .AtomicBoolean ;
5556
5657import io .reactivex .Completable ;
58+ import io .reactivex .schedulers .Schedulers ;
5759
5860/**
5961 * An AWS implementation of the {@link DataStorePlugin}.
6062 */
6163public final class AWSDataStorePlugin extends DataStorePlugin <Void > {
6264 private static final Logger LOG = Amplify .Logging .forNamespace ("amplify:aws-datastore" );
6365 private static final long PLUGIN_INIT_TIMEOUT_MS = TimeUnit .SECONDS .toMillis (5 );
66+ private static final long PLUGIN_TERMINATE_TIMEOUT_MS = TimeUnit .SECONDS .toMillis (5 );
6467 // Reference to an implementation of the Local Storage Adapter that
6568 // manages the persistence of data on-device.
6669 private final LocalStorageAdapter sqliteStorageAdapter ;
@@ -72,6 +75,8 @@ public final class AWSDataStorePlugin extends DataStorePlugin<Void> {
7275 // Keeps track of whether of not the category is initialized yet
7376 private final CountDownLatch categoryInitializationsPending ;
7477
78+ private final AtomicBoolean isOrchestratorReady ;
79+
7580 // Used to interrogate plugins, to understand if sync should be automatically turned on
7681 private final ApiCategory api ;
7782
@@ -90,6 +95,7 @@ private AWSDataStorePlugin(
9095 @ Nullable DataStoreConfiguration userProvidedConfiguration ) {
9196 this .sqliteStorageAdapter = SQLiteStorageAdapter .forModels (modelSchemaRegistry , modelProvider );
9297 this .categoryInitializationsPending = new CountDownLatch (1 );
98+ this .isOrchestratorReady = new AtomicBoolean (false );
9399 this .api = api ;
94100 this .orchestrator = new Orchestrator (
95101 modelProvider ,
@@ -192,13 +198,18 @@ public void configure(
192198 @ Override
193199 public void initialize (@ NonNull Context context ) throws AmplifyException {
194200 Throwable initError = initializeStorageAdapter (context )
195- .andThen (initializeOrchestrator ())
196201 .blockingGet (PLUGIN_INIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
197202 if (initError != null ) {
198203 throw new AmplifyException ("Failed to initialize the local storage adapter for the DataStore plugin." ,
199204 initError ,
200205 AmplifyException .TODO_RECOVERY_SUGGESTION );
201206 }
207+ // Kick off orchestrator asynchronously.
208+ synchronized (isOrchestratorReady ) {
209+ initializeOrchestrator ()
210+ .subscribeOn (Schedulers .io ())
211+ .subscribe ();
212+ }
202213 }
203214
204215 /**
@@ -219,8 +230,13 @@ private Completable initializeStorageAdapter(Context context) {
219230 */
220231 @ SuppressWarnings ("unused" )
221232 synchronized void terminate () throws AmplifyException {
222- orchestrator .stop ();
223- sqliteStorageAdapter .terminate ();
233+ Throwable throwable = orchestrator .stop ()
234+ .andThen (
235+ Completable .fromAction (sqliteStorageAdapter ::terminate )
236+ ).blockingGet (PLUGIN_TERMINATE_TIMEOUT_MS , TimeUnit .MILLISECONDS );
237+ if (throwable != null ) {
238+ LOG .warn ("An error occurred while terminating the DataStore plugin." , throwable );
239+ }
224240 }
225241
226242 /**
@@ -425,31 +441,66 @@ public <T extends Model> void observe(
425441 @ Override
426442 public void clear (@ NonNull Action onComplete ,
427443 @ NonNull Consumer <DataStoreException > onError ) {
428- beforeOperation (() -> {
429- orchestrator .stop ();
430- sqliteStorageAdapter .clear (onComplete , onError );
431- });
444+ // We shouldn't call beforeOperation when clearing the DataStore. The
445+ // only thing we have to wait for is the category initialization latch.
446+ boolean isCategoryInitialized = false ;
447+ try {
448+ isCategoryInitialized = categoryInitializationsPending .await (PLUGIN_INIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
449+ } catch (InterruptedException exception ) {
450+ LOG .warn ("Execution interrupted while waiting for DataStore to be initialized." );
451+ }
452+ if (!isCategoryInitialized ) {
453+ onError .accept (new DataStoreException ("DataStore not ready to be cleared." , "Retry your request." ));
454+ return ;
455+ }
456+ isOrchestratorReady .set (false );
457+ orchestrator .stop ()
458+ .subscribeOn (Schedulers .io ())
459+ .andThen (Completable .fromAction (() -> sqliteStorageAdapter .clear (() -> {
460+ // Invoke the consumer's callback once the clear operation is finished.
461+ onComplete .call ();
462+ // Kick off the orchestrator asynchronously.
463+ initializeOrchestrator ()
464+ .doOnError (throwable -> LOG .warn ("Failed to restart orchestrator after clearing." , throwable ))
465+ .doOnComplete (() -> LOG .info ("Orchestrator restarted after clear operation." ))
466+ .subscribe ();
467+ }, onError )))
468+ .doOnError (throwable -> LOG .warn ("Clear operation failed" , throwable ))
469+ .doOnComplete (() -> LOG .debug ("Clear operation completed." ))
470+ .subscribe ();
432471 }
433472
434473 private void beforeOperation (@ NonNull final Runnable runnable ) {
435- Completable opCompletable = Completable .fromAction (categoryInitializationsPending ::await );
436- if (!orchestrator .isStarted ()) {
437- opCompletable = opCompletable
438- .andThen (initializeOrchestrator ());
439- }
440- Throwable throwable = opCompletable
474+ Throwable throwable = Completable .fromAction (categoryInitializationsPending ::await )
475+ .repeatUntil (() -> {
476+ // Repeat until this is true or the blockingGet call times out.
477+ synchronized (isOrchestratorReady ) {
478+ return isOrchestratorReady .get ();
479+ }
480+ })
441481 .andThen (Completable .fromRunnable (runnable ))
442482 .blockingGet (PLUGIN_INIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
443- if (throwable != null ) {
444- LOG .warn ("Failed to execute request due to an unexpected error." , throwable );
483+ if (!(throwable == null && isOrchestratorReady .get ())) {
484+ if (!isOrchestratorReady .get ()) {
485+ LOG .warn ("Failed to execute request because DataStore is not fully initialized." );
486+ } else {
487+ LOG .warn ("Failed to execute request due to an unexpected error." , throwable );
488+ }
445489 }
446490 }
447491
448492 private Completable initializeOrchestrator () {
449493 if (api .getPlugins ().isEmpty ()) {
450494 return Completable .complete ();
451495 } else {
452- return orchestrator .start ();
496+ // Let's prevent the orchestrator startup from possibly running in main.
497+ return orchestrator .start (() -> {
498+ // This callback is invoked when the local storage observer gets initialized.
499+ isOrchestratorReady .set (true );
500+ })
501+ .repeatUntil (() -> isOrchestratorReady .get ())
502+ .observeOn (Schedulers .io ())
503+ .subscribeOn (Schedulers .io ());
453504 }
454505 }
455506
0 commit comments