Skip to content

Commit 9b76285

Browse files
committed
Update ObjectValue.buildProto to return the memoized proto if there are no overlay changes. Also addressed race condition due to missing lock.
1 parent 60e3bcb commit 9b76285

File tree

1 file changed

+34
-28
lines changed
  • firebase-firestore/src/main/java/com/google/firebase/firestore/model

1 file changed

+34
-28
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/model/ObjectValue.java

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
/** A structured object value stored in Firestore. */
3030
public final class ObjectValue implements Cloneable {
31+
private final Object lock = new Object(); // Monitor object
32+
3133
/**
3234
* The immutable Value proto for this object. Local mutations are stored in `overlayMap` and only
3335
* applied when {@link #buildProto()} is invoked.
@@ -124,11 +126,13 @@ private Value extractNestedValue(Value value, FieldPath fieldPath) {
124126
* invocations are based on this memoized result.
125127
*/
126128
private Value buildProto() {
127-
synchronized (overlayMap) {
128-
MapValue mergedResult = applyOverlay(FieldPath.EMPTY_PATH, overlayMap);
129-
if (mergedResult != null) {
130-
partialValue = Value.newBuilder().setMapValue(mergedResult).build();
131-
overlayMap.clear();
129+
synchronized (lock) {
130+
if (!overlayMap.isEmpty()) {
131+
MapValue mergedResult = applyOverlayLocked(FieldPath.EMPTY_PATH, overlayMap);
132+
if (mergedResult != null) {
133+
partialValue = Value.newBuilder().setMapValue(mergedResult).build();
134+
overlayMap.clear();
135+
}
132136
}
133137
}
134138
return partialValue;
@@ -171,31 +175,33 @@ public void setAll(Map<FieldPath, Value> data) {
171175
* Adds {@code value} to the overlay map at {@code path}. Creates nested map entries if needed.
172176
*/
173177
private void setOverlay(FieldPath path, @Nullable Value value) {
174-
Map<String, Object> currentLevel = overlayMap;
178+
synchronized (lock) {
179+
Map<String, Object> currentLevel = overlayMap;
175180

176-
for (int i = 0; i < path.length() - 1; ++i) {
177-
String currentSegment = path.getSegment(i);
178-
Object currentValue = currentLevel.get(currentSegment);
181+
for (int i = 0; i < path.length() - 1; ++i) {
182+
String currentSegment = path.getSegment(i);
183+
Object currentValue = currentLevel.get(currentSegment);
179184

180-
if (currentValue instanceof Map) {
181-
// Re-use a previously created map
182-
currentLevel = (Map<String, Object>) currentValue;
183-
} else if (currentValue instanceof Value
184-
&& ((Value) currentValue).getValueTypeCase() == Value.ValueTypeCase.MAP_VALUE) {
185-
// Convert the existing Protobuf MapValue into a Java map
186-
Map<String, Object> nextLevel =
187-
new HashMap<>(((Value) currentValue).getMapValue().getFieldsMap());
188-
currentLevel.put(currentSegment, nextLevel);
189-
currentLevel = nextLevel;
190-
} else {
191-
// Create an empty hash map to represent the current nesting level
192-
Map<String, Object> nextLevel = new HashMap<>();
193-
currentLevel.put(currentSegment, nextLevel);
194-
currentLevel = nextLevel;
185+
if (currentValue instanceof Map) {
186+
// Re-use a previously created map
187+
currentLevel = (Map<String, Object>) currentValue;
188+
} else if (currentValue instanceof Value
189+
&& ((Value) currentValue).getValueTypeCase() == Value.ValueTypeCase.MAP_VALUE) {
190+
// Convert the existing Protobuf MapValue into a Java map
191+
Map<String, Object> nextLevel =
192+
new HashMap<>(((Value) currentValue).getMapValue().getFieldsMap());
193+
currentLevel.put(currentSegment, nextLevel);
194+
currentLevel = nextLevel;
195+
} else {
196+
// Create an empty hash map to represent the current nesting level
197+
Map<String, Object> nextLevel = new HashMap<>();
198+
currentLevel.put(currentSegment, nextLevel);
199+
currentLevel = nextLevel;
200+
}
195201
}
196-
}
197202

198-
currentLevel.put(path.getLastSegment(), value);
203+
currentLevel.put(path.getLastSegment(), value);
204+
}
199205
}
200206

201207
/**
@@ -208,7 +214,7 @@ private void setOverlay(FieldPath path, @Nullable Value value) {
208214
* overlayMap}.
209215
* @return The merged data at `currentPath` or null if no modifications were applied.
210216
*/
211-
private @Nullable MapValue applyOverlay(
217+
private @Nullable MapValue applyOverlayLocked(
212218
FieldPath currentPath, Map<String, Object> currentOverlays) {
213219
boolean modified = false;
214220

@@ -227,7 +233,7 @@ private void setOverlay(FieldPath path, @Nullable Value value) {
227233
if (value instanceof Map) {
228234
@Nullable
229235
MapValue nested =
230-
applyOverlay(currentPath.append(pathSegment), (Map<String, Object>) value);
236+
applyOverlayLocked(currentPath.append(pathSegment), (Map<String, Object>) value);
231237
if (nested != null) {
232238
resultAtPath.putFields(pathSegment, Value.newBuilder().setMapValue(nested).build());
233239
modified = true;

0 commit comments

Comments
 (0)