Skip to content

Commit 849054a

Browse files
fix(datastore): Fix lock contention issue when running DataStore.start() from the callback of DataStore.stop() (#2208)
Co-authored-by: Michael Schneider <[email protected]>
1 parent ec510cf commit 849054a

File tree

3 files changed

+119
-5
lines changed

3 files changed

+119
-5
lines changed

aws-datastore/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ dependencies {
3838
testImplementation dependency.robolectric
3939
testImplementation dependency.androidx.test.core
4040
testImplementation dependency.mockk
41+
testImplementation project(path: ':aws-datastore')
42+
4143

44+
androidTestImplementation project(path: ':aws-datastore')
4245
androidTestImplementation dependency.mockito
4346
androidTestImplementation project(path: ':testmodels')
4447
androidTestImplementation project(path: ':testutils')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.datastore;
17+
18+
import android.content.Context;
19+
import androidx.annotation.RawRes;
20+
import androidx.test.core.app.ApplicationProvider;
21+
22+
import com.amplifyframework.AmplifyException;
23+
import com.amplifyframework.api.ApiCategory;
24+
import com.amplifyframework.core.Amplify;
25+
import com.amplifyframework.logging.AndroidLoggingPlugin;
26+
import com.amplifyframework.logging.LogLevel;
27+
import com.amplifyframework.testmodels.commentsblog.AmplifyModelProvider;
28+
import com.amplifyframework.testutils.Resources;
29+
import com.amplifyframework.testutils.sync.SynchronousDataStore;
30+
31+
import org.junit.AfterClass;
32+
import org.junit.BeforeClass;
33+
import org.junit.Test;
34+
35+
import java.util.concurrent.CountDownLatch;
36+
import java.util.concurrent.TimeUnit;
37+
38+
/**
39+
* Tests running DataStore.stop() and then calling DataStore.start() from within the stop() callback.
40+
* This is the recommended method for resetting sync expressions in the Amplify documentation.
41+
*/
42+
public final class StartStopInstrumentationTest {
43+
private static final int TIMEOUT_SECONDS = 60;
44+
private static SynchronousDataStore dataStore;
45+
private static DataStoreCategory dataStoreCategory;
46+
47+
/**
48+
* Set up Datastore plugin for testing.
49+
* @throws AmplifyException On failure to read config, setup API or DataStore categories
50+
*/
51+
@BeforeClass
52+
public static void setup() throws AmplifyException {
53+
Amplify.addPlugin(new AndroidLoggingPlugin(LogLevel.VERBOSE));
54+
55+
StrictMode.enable();
56+
Context context = ApplicationProvider.getApplicationContext();
57+
@RawRes int configResourceId = Resources.getRawResourceId(context, "amplifyconfigurationupdated");
58+
59+
ApiCategory apiCategory = new ApiCategory();
60+
61+
dataStoreCategory = DataStoreCategoryConfigurator.begin()
62+
.api(apiCategory)
63+
.clearDatabase(true)
64+
.context(context)
65+
.modelProvider(AmplifyModelProvider.getInstance())
66+
.resourceId(configResourceId)
67+
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
68+
.finish();
69+
dataStore = SynchronousDataStore.delegatingTo(dataStoreCategory);
70+
}
71+
72+
/**
73+
* Clear DataStore after testing to prevent conflict with any other test.
74+
*/
75+
@AfterClass
76+
public static void teardown() {
77+
if (dataStore != null) {
78+
try {
79+
dataStore.clear();
80+
} catch (Exception error) {
81+
// ok to ignore since problem encountered during tear down of the test.
82+
}
83+
}
84+
}
85+
86+
/**
87+
* Tests running DataStore.stop() and then calling DataStore.start() from within the stop() callback.
88+
* This is the recommended method for resetting sync expressions in the Amplify documentation.
89+
* @throws InterruptedException when interruption occurs during await().
90+
*/
91+
@Test
92+
public void testStartStop() throws InterruptedException {
93+
CountDownLatch latch = new CountDownLatch(2);
94+
dataStoreCategory.stop(() -> {
95+
latch.countDown();
96+
dataStoreCategory.start(
97+
latch::countDown,
98+
(error) -> {
99+
throw new RuntimeException(error);
100+
}
101+
);
102+
}, (error) -> {
103+
throw new RuntimeException(error);
104+
}
105+
);
106+
latch.await(10, TimeUnit.SECONDS);
107+
}
108+
}

aws-datastore/src/main/java/com/amplifyframework/datastore/syncengine/Orchestrator.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,14 @@ private Completable performSynchronized(Action action) {
183183
}
184184
LOG.info("Orchestrator lock acquired.");
185185
return Completable.fromAction(action)
186-
.doFinally(() -> {
187-
startStopSemaphore.release();
188-
LOG.info("Orchestrator lock released.");
189-
}
190-
);
186+
.andThen(
187+
Completable.fromAction(
188+
() -> {
189+
startStopSemaphore.release();
190+
LOG.info("Orchestrator lock released.");
191+
}
192+
)
193+
);
191194
}
192195

193196
private void unknownState(State state) throws DataStoreException {

0 commit comments

Comments
 (0)