Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get() reimplementation #3887

Merged
merged 32 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3e085d4
Updated getValue so that it doesn't update the SyncTree
maneesht Jul 8, 2022
34e251d
Fixed formatting issues
maneesht Jul 12, 2022
dcdff66
WIP
maneesht Jul 14, 2022
7b6f9f4
Merge remote-tracking branch 'origin/master' into mtewani/fix-get-cache
maneesht Jul 14, 2022
bec3e87
Merge remote-tracking branch 'origin/master' into mtewani/fix-get-cache
maneesht Jul 15, 2022
0276e1c
Merge remote-tracking branch 'origin/master' into mtewani/fix-get-cache
maneesht Jul 18, 2022
606b9c1
Used addEventRegistration and removeEventRegistration to add to the t…
maneesht Jul 19, 2022
299c2df
Fixed test and ran formatter
maneesht Jul 19, 2022
02af45d
Added comments
maneesht Jul 19, 2022
1babdc2
Fixed formatting
maneesht Jul 19, 2022
989f124
Added test annotation
maneesht Jul 19, 2022
8002947
WIP
maneesht Jul 25, 2022
b95be2c
get test suggest
jmwski Jul 20, 2022
6f5861a
WIP
maneesht Jul 26, 2022
3f3813a
Removed extra event propagation
maneesht Jul 27, 2022
13e2dc5
Included more tests
maneesht Jul 27, 2022
481acd7
Added missing link
maneesht Jul 27, 2022
69b4bef
Fixed formatting
maneesht Jul 27, 2022
cc6e3a1
Fixed formatting
maneesht Jul 27, 2022
cc70e55
Reverted retryrule to 3
maneesht Jul 27, 2022
1d58f9c
Resolved all TODOs
maneesht Jul 27, 2022
5cee9c7
Removed unnecessary nesting
maneesht Jul 27, 2022
d9a97f2
Removed event listener. WIP
maneesht Jul 27, 2022
23bde0b
Fixed tests
maneesht Jul 28, 2022
e60273c
Removed comment
maneesht Jul 28, 2022
94c91fc
Removed comment
maneesht Jul 28, 2022
844c8cc
Addressed comments
maneesht Jul 29, 2022
3bdf63a
Removed usage of 34L
maneesht Jul 29, 2022
a590590
Removed question
maneesht Jul 29, 2022
67bf0ce
Addressed comments
maneesht Jul 29, 2022
15bbeda
Fixed formatting
maneesht Jul 29, 2022
2d7b15f
Addressed comments
maneesht Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
public class ReadFuture implements Future<List<EventRecord>> {

public interface CompletionCondition {
public boolean isComplete(List<EventRecord> events);
public boolean isComplete(List<EventRecord> events)
throws ExecutionException, InterruptedException;
}

private List<EventRecord> events = new ArrayList<EventRecord>();
Expand Down Expand Up @@ -66,13 +67,15 @@ public void onDataChange(DataSnapshot snapshot) {
}
} catch (Exception e) {
exception = e;
ref.removeEventListener(valueEventListener);
finish();
}
}

@Override
public void onCancelled(DatabaseError error) {
wasCancelled = true;
ref.removeEventListener(valueEventListener);
finish();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
/**
* This class is an Android/SQL-backed implementation of PersistenceStorageEngine.
*
* <p>The implementation uses 3 tables for persistence: - writes: A list of all currently
* <p>The implementation uses 4 tables for persistence: - writes: A list of all currently
* outstanding (visible) writes. A row represents a single write containing a unique-across-restarts
* write id, the path string, the type which can be 'o' for an overwrite or 'm' for a merge, and the
* serialized node wrapped. Part number is NULL for normal writes. Large writes (>256K) are split
Expand Down Expand Up @@ -1058,6 +1058,7 @@ private Cursor loadNestedQuery(Path path, String[] columns) {
arguments[path.size() + 1] = pathPrefixStart;
arguments[path.size() + 2] = pathPrefixEnd;
String orderBy = PATH_COLUMN_NAME;
// TODO: We need to modify this to make sure that we limit based on trackedquerykeys

return database.query(SERVER_CACHE_TABLE, columns, whereClause, arguments, null, null, orderBy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ public Task<Object> get(List<String> path, Map<String, Object> queryParams) {
String status = (String) response.get(REQUEST_STATUS);
if (status.equals("ok")) {
Object body = response.get(SERVER_DATA_UPDATE_BODY);
delegate.onDataUpdate(query.path, body, /*isMerge=*/ false, /*tagNumber=*/ null);
source.setResult(body);
} else {
source.setException(new Exception((String) response.get(SERVER_DATA_UPDATE_BODY)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public Path(String pathString) {
count++;
}
}
// We are essentially doing the same work twice; once here, and once again in the constructor on
// line 60
pieces = new ChildKey[count];
int j = 0;
for (String segment : segments) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,13 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) {
*/
public Task<DataSnapshot> getValue(Query query) {
TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
final Repo repo = this;
this.scheduleNow(
new Runnable() {
@Override
public void run() {
// Always check active-listener in-memory caches first. These are always at least as
// up to date as the persistence cache.
// up to date as the persistence cache
Node serverValue = serverSyncTree.getServerValue(query.getSpec());
if (serverValue != null) {
source.setResult(
Expand Down Expand Up @@ -548,15 +549,42 @@ public void run() {
source.setException(Objects.requireNonNull(task.getException()));
}
} else {
/*
jmwski marked this conversation as resolved.
Show resolved Hide resolved
We need to replicate the behavior that occurs when running `once()`. In other words,
we need to create a new eventRegistration, register it with a view and then
overwrite the data at that location, and then remove the view.
*/
Node serverNode = NodeUtilities.NodeFromJSON(task.getResult());
postEvents(
serverSyncTree.applyServerOverwrite(query.getPath(), serverNode));
QuerySpec spec = query.getSpec();
// EventRegistrations require a listener to be attached, so a dummy
// ValueEventListener was created.
ValueEventListener listener =
new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
// noOp
}

@Override
public void onCancelled(@NonNull DatabaseError error) {
// noOp
}
};
ValueEventRegistration eventRegistration =
new ValueEventRegistration(repo, listener, spec);
serverSyncTree.addEventRegistration(eventRegistration, true);
jmwski marked this conversation as resolved.
Show resolved Hide resolved
if (spec.loadsAllData()) {
serverSyncTree.applyServerOverwrite(spec.getPath(), serverNode);
} else {
serverSyncTree.applyTaggedQueryOverwrite(
spec.getPath(), serverNode, getServerSyncTree().tagForQuery(spec));
}
source.setResult(
InternalHelpers.createDataSnapshot(
query.getRef(),
IndexedNode.from(serverNode, query.getSpec().getIndex())));
serverSyncTree.removeEventRegistration(eventRegistration, true);
jmwski marked this conversation as resolved.
Show resolved Hide resolved
}
serverSyncTree.setQueryInactive(query.getSpec());
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ public List<View> getQueryViews() {

public Node getCompleteServerCache(Path path) {
for (View view : this.views.values()) {
if (view.getCompleteServerCache(path) != null) {
return view.getCompleteServerCache(path);
Node serverCache = view.getCompleteServerCache(path);
if (serverCache != null) {
return serverCache;
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,14 @@ public Node getServerValue(QuerySpec query) {
});
}

/** Add an event callback for the specified query. */
public List<? extends Event> addEventRegistration(
@NotNull final EventRegistration eventRegistration) {
return addEventRegistration(eventRegistration, false);
}

/** Add an event callback for the specified query. */
public List<? extends Event> addEventRegistration(
@NotNull final EventRegistration eventRegistration, final boolean skipListenerSetup) {
return persistenceManager.runInTransaction(
new Callable<List<? extends Event>>() {
@Override
Expand Down Expand Up @@ -635,7 +640,7 @@ public List<? extends Event> call() {
WriteTreeRef writesCache = pendingWriteTree.childWrites(path);
List<? extends Event> events =
syncPoint.addEventRegistration(eventRegistration, writesCache, serverCache);
if (!viewAlreadyExists && !foundAncestorDefaultView) {
if (!viewAlreadyExists && !foundAncestorDefaultView && !skipListenerSetup) {
View view = syncPoint.viewForQuery(query);
setupListener(query, view);
}
Expand All @@ -650,7 +655,19 @@ public List<? extends Event> call() {
* <p>If query is the default query, we'll check all queries for the specified eventRegistration.
*/
public List<Event> removeEventRegistration(@NotNull EventRegistration eventRegistration) {
return this.removeEventRegistration(eventRegistration.getQuerySpec(), eventRegistration, null);
return this.removeEventRegistration(
eventRegistration.getQuerySpec(), eventRegistration, null, false);
}

public List<Event> removeEventRegistration(
@NotNull EventRegistration eventRegistration, boolean skipDedup) {
return this.removeEventRegistration(
eventRegistration.getQuerySpec(), eventRegistration, null, skipDedup);
}

public List<Event> removeEventRegistration(
QuerySpec query, @NotNull EventRegistration eventRegistration) {
return this.removeEventRegistration(query, eventRegistration, null, false);
}

/**
Expand All @@ -660,13 +677,14 @@ public List<Event> removeEventRegistration(@NotNull EventRegistration eventRegis
*/
public List<Event> removeAllEventRegistrations(
@NotNull QuerySpec query, @NotNull DatabaseError error) {
return this.removeEventRegistration(query, null, error);
jmwski marked this conversation as resolved.
Show resolved Hide resolved
return this.removeEventRegistration(query, null);
}

private List<Event> removeEventRegistration(
final @NotNull QuerySpec query,
final @Nullable EventRegistration eventRegistration,
final @Nullable DatabaseError cancelError) {
final @Nullable DatabaseError cancelError,
final boolean skipDedup) {
return persistenceManager.runInTransaction(
new Callable<List<Event>>() {
@Override
Expand All @@ -688,6 +706,7 @@ public List<Event> call() {
if (maybeSyncPoint.isEmpty()) {
syncPointTree = syncPointTree.remove(path);
}

List<QuerySpec> removed = removedAndEvents.getFirst();
cancelEvents = removedAndEvents.getSecond();
// We may have just removed one of many listeners and can short-circuit this whole
Expand All @@ -701,6 +720,25 @@ public List<Event> call() {
persistenceManager.setQueryInactive(query);
removingDefault = removingDefault || queryRemoved.loadsAllData();
}

/**
* This is to handle removeRegistration by {@link Repo#getValue(Query)}. Specifically
* to avoid the scenario where: A listener is attached at a child path, and {@link
* Repo#getValue(Query)} is called on the parent path. Normally, when a listener is
* attached on a child path and then a parent path has a listener attached to it, to
* reduce the number of listeners, the listen() function will unlisten to the child
* path and listen instead on the parent path. And then, when removeRegistration is
* called on the parent path, the child path will get listened to, since it doesn't
* have anything covering its path. However, for {@link Repo#getValue(Query)}, we do
* not call listen on the parent path, and the child path is still listened to and so
* when the deduping happens below, the SyncTree assumes that the child listener has
* been removed and attempts to call listen again, but since we are still listening on
* that location, listen would be called twice on the same query. skipDedup allows us
* to skip this deduping process altogether.
*/
if (skipDedup) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are leaking the tag created inside keepSynced(true) in Repo.getValue because we never hit L790.

return null;
}
ImmutableTree<SyncPoint> currentTree = syncPointTree;
boolean covered =
currentTree.getValue() != null && currentTree.getValue().hasCompleteView();
Expand Down Expand Up @@ -915,7 +953,7 @@ private QuerySpec queryForTag(Tag tag) {
}

/** Return the tag associated with the given query. */
private Tag tagForQuery(QuerySpec query) {
public Tag tagForQuery(QuerySpec query) {
return this.queryToTagMap.get(query);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public void zombifyForRemove(EventRegistration registration) {
if (registrationList != null && !registrationList.isEmpty()) {
if (registration.getQuerySpec().isDefault()) {
// The behavior here has to match the behavior of SyncPoint.removeEventRegistration.
// If the query is default, it remove a single instance of the registration
// If the query is default, it removes a single instance of the registration
// from each unique query. So for example, if you had 3 copies registered under default,
// you would end up with 2 still registered.
// If you had 1 registration in default and 2 in query a', you'd end up with just
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public Node getCompleteServerCache(Path path) {
if (cache != null) {
// If this isn't a "loadsAllData" view, then cache isn't actually a complete cache and
// we need to see if it contains the child we're interested in.
// Clarify: just because this is a loadsAllData() doesn't mean that it'll contain the child,
// right?
jmwski marked this conversation as resolved.
Show resolved Hide resolved
if (this.query.loadsAllData()
|| (!path.isEmpty() && !cache.getImmediateChild(path.getFront()).isEmpty())) {
return cache.getChild(path);
Expand Down