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

Change Query.idMap to count ids, to be defensive against duplicate ids in query results #253

Merged
merged 4 commits into from
Jul 28, 2017
Merged

Change Query.idMap to count ids, to be defensive against duplicate ids in query results #253

merged 4 commits into from
Jul 28, 2017

Conversation

ericyhwang
Copy link
Contributor

There's a ShareDB issue where a subscribed ShareDB query can emit an insert event with an id that's already present in the result list. ShareDB then immediately emits an event (usually remove) to fix the duplication, but because the Racer query's idMap didn't keep track of id counts, it would mistakenly remove the id when the id's count went from 2 to 1. That causes the doc to become unloaded, since Racer uses the idMap to determine if any queries are subscribed to the doc.

The underlying ShareDB issue requires more investigation to fix, but Racer should still be defensive about it, especially since the issue results in Racer becoming internally inconsistent:

> query.getIds()
[ 'f6...',
  'c8...',
  'a3...',
  '37...' ]
> query.get()
[ undefined, undefined, undefined, undefined ]
> query.idMap
{}

@@ -158,7 +158,7 @@ function Query(model, collectionName, expression, options) {
// because otherwise maybeUnload would be looping through the entire results
// set of each query on the same collection for every doc checked
//
// Map of id -> true
// Map of id -> count of ids
this.idMap = {};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also noticed that subscriptions.js does a plain property check on idMap instead of using hasOwnProperty, which means that the check will be incorrect if an id happens to be the name of an Object prototype property. Do you think that matters enough to fix? Racer-generated ids are UUIDs, so it's only a problem for manually-specified ids.

Copy link
Contributor

Choose a reason for hiding this comment

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

True. The problem would result in a doc not getting locally cleaned up, so honestly, the impact isn't likely to be a concern. I'd probably not worry about it for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, since idMap is now a number, we could use the check idMap[id] > 0, which might be better since it is more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done.

Copy link
Contributor

@nateps nateps left a comment

Choose a reason for hiding this comment

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

Hey! A few small comments on style mostly.

@@ -312,7 +317,8 @@ Query.prototype._removeMapIds = function(ids) {
Query.prototype._addMapIds = function(ids) {
for (var i = ids.length; i--;) {
var id = ids[i];
this.idMap[id] = true;
this.idMap[id] = this.idMap[id] || 0;
this.idMap[id]++;
Copy link
Contributor

Choose a reason for hiding this comment

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

Equivalently, you can write this: this.idMap[id] = (this.idMap[id] || 0) + 1;

It is slightly more complexity on one line, and I don't even know if it makes a difference once optimized, but avoiding two different property gets + assignments might be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
if (this.idMap[id] === 0) {
delete this.idMap[id];
}
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 it is slightly more clear to write:

if (this.idMap[id] > 1) {
  this.idMap[id]--;
} else {
  delete this.idMap[id];
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The two are not quite the same.

In your proposal, any ids that hit zero wouldn't get cleaned up in the map unless _removeMapIds happened to get called again with one of those zero-ed out ids.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh wait, never mind. The condition is > 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, this is done now too, thanks for the suggestion! It's one fewer conditional check too, which is nice.

@@ -158,7 +158,7 @@ function Query(model, collectionName, expression, options) {
// because otherwise maybeUnload would be looping through the entire results
// set of each query on the same collection for every doc checked
//
// Map of id -> true
// Map of id -> count of ids
this.idMap = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, since idMap is now a number, we could use the check idMap[id] > 0, which might be better since it is more explicit.

@nateps
Copy link
Contributor

nateps commented Jul 28, 2017

LGTM!

@nateps nateps merged commit 70791bc into derbyjs:master Jul 28, 2017
@ericyhwang ericyhwang deleted the fix-query-idMap branch July 28, 2017 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants