Prevent CachedContentIndex.idToKey from growing without bound

PiperOrigin-RevId: 246727723
This commit is contained in:
olly 2019-05-05 17:25:48 +01:00 committed by Oliver Woodman
parent 1292a35ec6
commit b53ac32b8c

View File

@ -89,6 +89,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* efficiently when the index is next stored. * efficiently when the index is next stored.
*/ */
private final SparseBooleanArray removedIds; private final SparseBooleanArray removedIds;
/** Tracks ids that are new since the index was last stored. */
private final SparseBooleanArray newIds;
private Storage storage; private Storage storage;
@Nullable private Storage previousStorage; @Nullable private Storage previousStorage;
@ -150,6 +152,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
keyToContent = new HashMap<>(); keyToContent = new HashMap<>();
idToKey = new SparseArray<>(); idToKey = new SparseArray<>();
removedIds = new SparseBooleanArray(); removedIds = new SparseBooleanArray();
newIds = new SparseBooleanArray();
Storage databaseStorage = Storage databaseStorage =
databaseProvider != null ? new DatabaseStorage(databaseProvider) : null; databaseProvider != null ? new DatabaseStorage(databaseProvider) : null;
Storage legacyStorage = Storage legacyStorage =
@ -206,6 +209,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
idToKey.remove(removedIds.keyAt(i)); idToKey.remove(removedIds.keyAt(i));
} }
removedIds.clear(); removedIds.clear();
newIds.clear();
} }
/** /**
@ -250,11 +254,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
CachedContent cachedContent = keyToContent.get(key); CachedContent cachedContent = keyToContent.get(key);
if (cachedContent != null && cachedContent.isEmpty() && !cachedContent.isLocked()) { if (cachedContent != null && cachedContent.isEmpty() && !cachedContent.isLocked()) {
keyToContent.remove(key); keyToContent.remove(key);
storage.onRemove(cachedContent); int id = cachedContent.id;
// Keep an entry in idToKey to stop the id from being reused until the index is next stored. boolean neverStored = newIds.get(id);
idToKey.put(cachedContent.id, /* value= */ null); storage.onRemove(cachedContent, neverStored);
// Track that the entry should be removed from idToKey when the index is next stored. if (neverStored) {
removedIds.put(cachedContent.id, /* value= */ true); // The id can be reused immediately.
idToKey.remove(id);
newIds.delete(id);
} else {
// Keep an entry in idToKey to stop the id from being reused until the index is next stored,
// and add an entry to removedIds to track that it should be removed when this does happen.
idToKey.put(id, /* value= */ null);
removedIds.put(id, /* value= */ true);
}
} }
} }
@ -297,8 +309,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private CachedContent addNew(String key) { private CachedContent addNew(String key) {
int id = getNewId(idToKey); int id = getNewId(idToKey);
CachedContent cachedContent = new CachedContent(id, key); CachedContent cachedContent = new CachedContent(id, key);
keyToContent.put(cachedContent.key, cachedContent); keyToContent.put(key, cachedContent);
idToKey.put(cachedContent.id, cachedContent.key); idToKey.put(id, key);
newIds.put(id, true);
storage.onUpdate(cachedContent); storage.onUpdate(cachedContent);
return cachedContent; return cachedContent;
} }
@ -435,7 +448,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
* Ensures incremental changes to the index since the initial {@link #initialize(long)} or last * Ensures incremental changes to the index since the initial {@link #initialize(long)} or last
* {@link #storeFully(HashMap)} are persisted. The storage will have been notified of all such * {@link #storeFully(HashMap)} are persisted. The storage will have been notified of all such
* changes via {@link #onUpdate(CachedContent)} and {@link #onRemove(CachedContent)}. * changes via {@link #onUpdate(CachedContent)} and {@link #onRemove(CachedContent, boolean)}.
* *
* @param content The key to content map to persist. * @param content The key to content map to persist.
* @throws IOException If an error occurs persisting the index. * @throws IOException If an error occurs persisting the index.
@ -453,8 +466,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* Called when a {@link CachedContent} is removed. * Called when a {@link CachedContent} is removed.
* *
* @param cachedContent The removed {@link CachedContent}. * @param cachedContent The removed {@link CachedContent}.
* @param neverStored True if the {@link CachedContent} was added more recently than when the
* index was last stored.
*/ */
void onRemove(CachedContent cachedContent); void onRemove(CachedContent cachedContent, boolean neverStored);
} }
/** {@link Storage} implementation that uses an {@link AtomicFile}. */ /** {@link Storage} implementation that uses an {@link AtomicFile}. */
@ -540,7 +555,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
@Override @Override
public void onRemove(CachedContent cachedContent) { public void onRemove(CachedContent cachedContent, boolean neverStored) {
changed = true; changed = true;
} }
@ -856,9 +871,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
@Override @Override
public void onRemove(CachedContent cachedContent) { public void onRemove(CachedContent cachedContent, boolean neverStored) {
if (neverStored) {
pendingUpdates.delete(cachedContent.id);
} else {
pendingUpdates.put(cachedContent.id, null); pendingUpdates.put(cachedContent.id, null);
} }
}
private Cursor getCursor() { private Cursor getCursor() {
return databaseProvider return databaseProvider