Skip to content

Commit da10afb

Browse files
fix(aws-datastore): idempotent deletions from merger (#1014)
When deletions are merged to the local store, the storage adapter might throw an error, if there's nothing left to delete. The existing code tries to work around this by first checking if there is a matching instance before attempting to delete it. Instead, we will swallow deletion failures and log a verbose message. Resolves: #1011
1 parent ddc7a23 commit da10afb

File tree

2 files changed

+30
-64
lines changed

2 files changed

+30
-64
lines changed

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

Lines changed: 27 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,14 @@
1717

1818
import androidx.annotation.NonNull;
1919

20-
import com.amplifyframework.core.Action;
2120
import com.amplifyframework.core.Amplify;
2221
import com.amplifyframework.core.Consumer;
2322
import com.amplifyframework.core.NoOpConsumer;
2423
import com.amplifyframework.core.model.Model;
25-
import com.amplifyframework.core.model.query.Where;
2624
import com.amplifyframework.core.model.query.predicate.QueryPredicates;
2725
import com.amplifyframework.datastore.DataStoreChannelEventName;
2826
import com.amplifyframework.datastore.appsync.ModelMetadata;
2927
import com.amplifyframework.datastore.appsync.ModelWithMetadata;
30-
import com.amplifyframework.datastore.appsync.SerializedModel;
3128
import com.amplifyframework.datastore.storage.LocalStorageAdapter;
3229
import com.amplifyframework.datastore.storage.StorageItemChange;
3330
import com.amplifyframework.hub.HubChannel;
@@ -75,13 +72,14 @@ <T extends Model> Completable merge(ModelWithMetadata<T> modelWithMetadata) {
7572

7673
/**
7774
* Merge an item back into the local store, using a default strategy.
75+
* TODO: Change this method to return a Maybe, and remove the Consumer argument.
7876
* @param modelWithMetadata A model, combined with metadata about it
79-
* @param storageItemChangeConsumer A callback invoked when the merge method saves or deletes the model.
77+
* @param changeTypeConsumer A callback invoked when the merge method saves or deletes the model.
8078
* @param <T> Type of model
8179
* @return A completable operation to merge the model
8280
*/
83-
<T extends Model> Completable merge(ModelWithMetadata<T> modelWithMetadata,
84-
Consumer<StorageItemChange<T>> storageItemChangeConsumer) {
81+
<T extends Model> Completable merge(
82+
ModelWithMetadata<T> modelWithMetadata, Consumer<StorageItemChange.Type> changeTypeConsumer) {
8583
ModelMetadata metadata = modelWithMetadata.getSyncMetadata();
8684
boolean isDelete = Boolean.TRUE.equals(metadata.isDeleted());
8785
int incomingVersion = metadata.getVersion() == null ? -1 : metadata.getVersion();
@@ -103,7 +101,7 @@ <T extends Model> Completable merge(ModelWithMetadata<T> modelWithMetadata,
103101
.filter(currentVersion -> currentVersion == -1 || incomingVersion > currentVersion)
104102
// If we should merge, then do so now, starting with the model data.
105103
.flatMapCompletable(shouldMerge ->
106-
(isDelete ? delete(model, storageItemChangeConsumer) : save(model, storageItemChangeConsumer))
104+
(isDelete ? delete(model, changeTypeConsumer) : save(model, changeTypeConsumer))
107105
.andThen(save(metadata, NoOpConsumer.create()))
108106
)
109107
// Let the world know that we've done a good thing.
@@ -128,63 +126,35 @@ private <T extends Model> void announceSuccessfulMerge(ModelWithMetadata<T> mode
128126
}
129127

130128
// Delete a model.
131-
private <T extends Model> Completable delete(T model, Consumer<StorageItemChange<T>> onStorageItemChange) {
132-
return Completable.defer(() -> Completable.create(emitter -> {
133-
// First, check if the thing exists.
134-
// If we don't, we'll get an exception saying basically,
135-
// "failed to delete a non-existing thing."
136-
ifPresent(model,
137-
() -> localStorageAdapter.delete(
138-
model,
139-
StorageItemChange.Initiator.SYNC_ENGINE,
140-
QueryPredicates.all(),
141-
storageItemChange -> {
142-
onStorageItemChange.accept(storageItemChange);
143-
emitter.onComplete();
144-
},
145-
emitter::onError
146-
),
147-
emitter::onComplete
148-
);
149-
}));
129+
private <T extends Model> Completable delete(T model, Consumer<StorageItemChange.Type> changeTypeConsumer) {
130+
return Completable.create(emitter ->
131+
localStorageAdapter.delete(model, StorageItemChange.Initiator.SYNC_ENGINE, QueryPredicates.all(),
132+
storageItemChange -> {
133+
changeTypeConsumer.accept(storageItemChange.type());
134+
emitter.onComplete();
135+
},
136+
failure -> {
137+
LOG.verbose(
138+
"Failed to delete a model while merging. Perhaps it was already gone? "
139+
+ android.util.Log.getStackTraceString(failure)
140+
);
141+
changeTypeConsumer.accept(StorageItemChange.Type.DELETE);
142+
emitter.onComplete();
143+
}
144+
)
145+
);
150146
}
151147

152148
// Create or update a model.
153-
private <T extends Model> Completable save(T model, Consumer<StorageItemChange<T>> onStorageItemChange) {
154-
return Completable.defer(() -> Completable.create(emitter ->
155-
localStorageAdapter.save(
156-
model,
157-
StorageItemChange.Initiator.SYNC_ENGINE,
158-
QueryPredicates.all(),
149+
private <T extends Model> Completable save(T model, Consumer<StorageItemChange.Type> changeTypeConsumer) {
150+
return Completable.create(emitter ->
151+
localStorageAdapter.save(model, StorageItemChange.Initiator.SYNC_ENGINE, QueryPredicates.all(),
159152
storageItemChange -> {
160-
onStorageItemChange.accept(storageItemChange);
153+
changeTypeConsumer.accept(storageItemChange.type());
161154
emitter.onComplete();
162155
},
163156
emitter::onError
164157
)
165-
));
166-
}
167-
168-
/**
169-
* If the DataStore contains a model instance, then perform an action.
170-
* Otherwise, perform some other action.
171-
* @param model A model that might exist in local storage
172-
* @param onPresent If there is a match, perform this action
173-
* @param onNotPresent If there is NOT a match, perform this action as a fallback
174-
*/
175-
private void ifPresent(Model model, Action onPresent, Action onNotPresent) {
176-
final String modelName;
177-
if (model instanceof SerializedModel) {
178-
modelName = ((SerializedModel) model).getModelName();
179-
} else {
180-
modelName = model.getClass().getSimpleName();
181-
}
182-
localStorageAdapter.query(modelName, Where.id(model.getId()), iterator -> {
183-
if (iterator.hasNext()) {
184-
onPresent.call();
185-
} else {
186-
onNotPresent.call();
187-
}
188-
}, failure -> onNotPresent.call());
158+
);
189159
}
190160
}

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@
1515

1616
package com.amplifyframework.datastore.syncengine;
1717

18-
import com.amplifyframework.core.Amplify;
19-
import com.amplifyframework.core.model.Model;
2018
import com.amplifyframework.datastore.events.ModelSyncedEvent;
2119
import com.amplifyframework.datastore.storage.StorageItemChange;
22-
import com.amplifyframework.logging.Logger;
2320

2421
import java.util.Map;
2522
import java.util.concurrent.ConcurrentHashMap;
@@ -30,7 +27,6 @@
3027
* by operation type for a given model.
3128
*/
3229
final class ModelSyncMetricsAccumulator {
33-
private static final Logger LOG = Amplify.Logging.forNamespace("amplify:aws-datastore");
3430
private final Map<StorageItemChange.Type, AtomicInteger> syncMetrics;
3531
private final String modelClassName;
3632

@@ -62,9 +58,9 @@ public ModelSyncedEvent toModelSyncedEvent(SyncType syncType) {
6258

6359
/**
6460
* Increments the counter for a given change type.
65-
* @param itemChange The change type to increment.
61+
* @param changeType The change type to increment.
6662
*/
67-
public void increment(StorageItemChange<? extends Model> itemChange) {
68-
syncMetrics.get(itemChange.type()).incrementAndGet();
63+
public void increment(StorageItemChange.Type changeType) {
64+
syncMetrics.get(changeType).incrementAndGet();
6965
}
7066
}

0 commit comments

Comments
 (0)