Skip to content

Conversation

adamchel
Copy link
Contributor

This is complete to the best of my knowledge and based on offline conversations I've had with Eric, but I threw in some comments where I'm still unsure if stuff needs to be done or if it should be delegated to future tickets.

The tests from 1950 (specifically testConfigure) are still flaky, but otherwise everything passes.

I'll keep an eye on evergreen as well.

@adamchel adamchel requested review from edaniels and jsflax October 11, 2018 22:28
@coveralls
Copy link

coveralls commented Oct 11, 2018

Coverage Status

Coverage increased (+4.4%) to 59.766% when pulling be063aa on adamchel:STITCH-1954-wip-rebased into a28ddad on mongodb:master.

Copy link
Contributor

@jsflax jsflax left a comment

Choose a reason for hiding this comment

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

Looking good! One possible minor and some q's.

// TODO(QUESTION FOR REVIEW): The testFrozenDocumentConfig test fails if I call this, but
// other tests pass. Shouldn't the receipt of this document indicate that this is the last
// known remote version of this document?
// docConfig.setLastKnownRemoteVersion(lastKnownRemoteVersion);
Copy link
Contributor

Choose a reason for hiding this comment

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

[q] Where does testFrozenDocumentConfig fail?

Copy link
Contributor Author

@adamchel adamchel Oct 12, 2018

Choose a reason for hiding this comment

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

no longer relevant, worked it out with Eric and we now* have the correct behavior

final BsonValue documentId,
final BsonDocument document,
final BsonValue atVersion,
final BsonDocument atVersion,
Copy link
Contributor

Choose a reason for hiding this comment

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

[q] Why are we passing this around as a BsonDocument? Seems it may be cleaner to use its concrete type here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because the consumer of atVersion is only operating on the pure BSON of the version, whereas DocumentVersionInfo is an abstraction on top of that that we use for various other checks.

DocumentVersionInfo is allowed to represent an empty version, meaning consumers of atVersion would then have to check for those kinds of states. I think this is fine for now, especially since consumers of atVersion never convert back to a DocumentVersionInfo, and they don't have any use for its methods/functionality

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

// old version, rather than the new version. Is this intentional? It seems like we should be
// setting the pending writes at the newly generated version. I'm confused though because if I
// use the new version here instead, tests fail because updates don't end up happening on a
// sync pass.
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to have had a conflict where the local version is accepted to get to this point. However, I think atVersion is really a misnomer for lastKnownRemoteVersion, as seen in the CoreDocumentSynchronizationConfig. So at this point in the code, we are preparing the document with the next version, but the pending writes are still currently at the lastKnownRemoteVersion, which is the previous version, since we are not writing to remote yet.

On the next pass, in syncLocalToRemote (since we have resolved the prior conflict), our document will be written to the remote, and setPendingWritesComplete will be called with the version within the document, which is now the lastKnownRemoteVersion.

I'd like @edaniels to double check my logic here, but I believe this is the reasoning. By committing this version to the config, we've essentially told the system that the conflict has been resolved and that this is a locally generated change event. Via both this and the logicalT checks, it will prevent this change event from getting mistakenly consumed in a R2L pass.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm also thinking it could be nice to have this tested as a unit test.

Copy link
Contributor

Choose a reason for hiding this comment

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

discussed offline. this is correct

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

return versionValues != null;
}

private @Nonnull VersionValues getVersionValuesThrowIfNull() {
Copy link
Contributor

Choose a reason for hiding this comment

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

[minor] Using ThrowIfNull as a suffix here, and the exception thrown here, is arguably a bit of an anti-pattern. If this is an illegal state, we should throw that exception in the constructor– otherwise, it's on the user to call the #isNonEmptyVersion check.

) {
if (version != null) {
this.versionDoc = version;
this.versionValues = new VersionValues(
Copy link
Contributor

Choose a reason for hiding this comment

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

[minor][related to comment below] We should be checking that these keys exist on the versionDoc before reading them.

If it the doc is null or they do not exist and this is a disallowed state, we should throw an IllegalStateException.

Copy link
Contributor

Choose a reason for hiding this comment

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

From what I can tell here, if the versionDoc is null, that should be an OK state, and is covered by isNonEmptyVersion. Maybe the exception should be if the versionDoc is malformed? Just food for thought– I think to an extent, there's a stylistic choice here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely agree that this class is a little funky, I refactored it a little bit per your comments and eric's comments to make it harder to reach an illegal state.

Copy link
Contributor

@edaniels edaniels left a comment

Choose a reason for hiding this comment

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

Generally LGTM. There are tests to fix with the has committed writes.


assertTrue(coll.syncedIds.contains(doc1Id))

// syncing on this document with an unsupported spv should cause the document to desync
Copy link
Contributor

Choose a reason for hiding this comment

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

[test] Check that we emitted an error for this

docConfig,
remoteChangeEvent);
} else {
// TODO(ERIC->ADAM): This event may contain old version info. We should be filtering out
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace ERIC->ADAM with STITCH-1972

}
}

private static final String SYNC_PROTOCOL_VERSION_FIELD = "spv";
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] Move this into a Fields inner static class

* Returns whether this version is non-empty (i.e. a version from a document with no version)
* @return true if the version is non-empty, false if the version is empty.
*/
boolean isNonEmptyVersion() {
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] easier to call this hasVersion

return versionValues != null;
}

private @Nonnull VersionValues getVersionValuesThrowIfNull() {
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] I would call this getVersion and make the class called a Version, not VersionValues

return this.versionValues;
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] These methods should be on the version, not the class.

// TODO(QFR): I ask because when we are looking at hasCommittedVersion, we are using the
// lastKnownRemoteVersion of the doc config rather than the last known remote version based
// on the incoming change event we got here, which would make this not conform to spec.
if (docConfig.hasCommittedVersion(localVersion)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[minor] This should just be remote version

final BsonDocument localDocument = localColl.find(docFilter).first();

final BsonValue lastKnownRemoteVersion;
final BsonDocument lastKnownRemoteVersion;
Copy link
Contributor

Choose a reason for hiding this comment

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

if it helps, name this currentRemoteVersion which will become lastKnownRemoteVersion in the config

return;
}

// TODO(QUESTION FOR REVIEW): The testFrozenDocumentConfig test fails if I call this, but
Copy link
Contributor

Choose a reason for hiding this comment

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

There reason not to set this and why you can get rid of that set method is this would always make the next statement true.

// old version, rather than the new version. Is this intentional? It seems like we should be
// setting the pending writes at the newly generated version. I'm confused though because if I
// use the new version here instead, tests fail because updates don't end up happening on a
// sync pass.
Copy link
Contributor

Choose a reason for hiding this comment

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

discussed offline. this is correct

@adamchel
Copy link
Contributor Author

@edaniels @jsflax this is ready for another look. On top of resolving your feedback, I also did some general cleanup in DataSynchronizer.java to get rid of redundant code and code that was made unnecessary with some of the other refactors that we made

Copy link
Contributor

@edaniels edaniels left a comment

Choose a reason for hiding this comment

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

LGTM. Just a unit test class to add

* empty version.
* @return a BsonDocument representing a synchronization version
*/
BsonDocument getNextVersion() {
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] Add a unit test(s) for this

Copy link
Contributor

@jsflax jsflax left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@jsflax
Copy link
Contributor

jsflax commented Oct 12, 2018

Could use a unit test for the logic we spoke about earlier in this PR.

@adamchel adamchel merged commit d9d9d64 into mongodb:master Oct 12, 2018
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.

4 participants