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

C++ migration: port FSTTransaction #2362

Merged
merged 170 commits into from
Feb 12, 2019
Merged

C++ migration: port FSTTransaction #2362

merged 170 commits into from
Feb 12, 2019

Conversation

var-const
Copy link
Contributor

No description provided.

*/
util::Status last_write_error_;

std::unordered_map<model::DocumentKey,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FSTTransaction used a SortedMap, but it seems to me that unordered_map is more appropriate here: order does not matter, and the map isn't shared across different objects.

Copy link
Member

Choose a reason for hiding this comment

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

FSTTransaction used a SortedMap

Technically, it uses a std::map, right? (Just to make sure we're looking at the same thing). According to our git history, this used to be a NSMutableDictionary. So I suspect the conversion from that to std::map was slightly wrong, and it should've been unsorted_map instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right... It's the Web SDK that uses a SortedMap, which I think I originally duplicated and then thought that SortedMap was there from the start. Android uses the standard library HashMap.


absl::optional<SnapshotVersion> Transaction::GetVersion(
const DocumentKey& key) const {
auto found = read_versions_.find(key);
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 method doesn't exist on other platforms, but makes the code accessing the map more similar (other platforms use some Get which returns an element or null).

@@ -177,7 +178,9 @@ class RemoteStore : public TargetMetadataProvider,
void AddToWritePipeline(FSTMutationBatch* batch);

/** Returns a new transaction backed by this remote store. */
FSTTransaction* CreateTransaction();
// TODO(c++14): return a plain value when it becomes possible to move
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Returning a shared_ptr is largely an optimization. The transaction is copied into a lambda more than once, and moving into a lamdba is not possible in C++11.

Copy link
Member

Choose a reason for hiding this comment

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

Copying shared pointers around is kinda slow too. Depending on it's size/complexity, you might be better off just copying the underlying object. (I wrote up a quick/naive benchmark and observed that for small objects, it was faster to just copy them. The cut-over point was ~4k for my (simple) test object, though I would imagine it would be smaller for a core::Transaction.)

It doesn't really matter here... any optimization we do will be dwarfed by the round trip times to the server that a transaction requires. Feel free to keep this as a shared_ptr or convert to plain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed that it's unlikely to be a big deal either way. A couple of arguments in favor of shared_ptr:

  • Transaction has a vector and an unordered_map as its members, which require dynamic memory allocation;
  • once we start using C++ Mutation, the most natural way to store it would be as vector<unique_ptr<Mutation>>, which would make the class uncopyable (of course it's possible to write a custom copy constructor, but using shared_ptr for the whole Transaction now simply avoids the issue).

Copy link
Member

Choose a reason for hiding this comment

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

sgtm

Copy link
Contributor

Choose a reason for hiding this comment

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

Does each lambda has to retain an ownership of Transaction? I expect each lambda only use the passed in Transaction instead of retain an ownership of it. Whoever started a transaction would be the sole owner of the Transaction.

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 problem is the lambda in FSTSyncEngine.mm. It creates a Transaction as a local variable and immediately passes it into a lambda.

absl::optional<SnapshotVersion> existing_version = GetVersion(doc.key);
if (existing_version.has_value()) {
if (doc_version != existing_version.value()) {
return Status{// This transaction will fail no matter what.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed, this changes the behavior -- previously, iOS would unconditionally forbid reading the same document more than once. The new behavior is same as other platforms.

@var-const var-const changed the title [DO NOT MERGE] [NOT READY FOR REVIEW] C++ migration: port FSTTransaction Feb 9, 2019
@var-const var-const requested review from rsgowman and zxu123 February 9, 2019 00:01
@@ -177,7 +178,9 @@ class RemoteStore : public TargetMetadataProvider,
void AddToWritePipeline(FSTMutationBatch* batch);

/** Returns a new transaction backed by this remote store. */
FSTTransaction* CreateTransaction();
// TODO(c++14): return a plain value when it becomes possible to move
Copy link
Member

Choose a reason for hiding this comment

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

Copying shared pointers around is kinda slow too. Depending on it's size/complexity, you might be better off just copying the underlying object. (I wrote up a quick/naive benchmark and observed that for small objects, it was faster to just copy them. The cut-over point was ~4k for my (simple) test object, though I would imagine it would be smaller for a core::Transaction.)

It doesn't really matter here... any optimization we do will be dwarfed by the round trip times to the server that a transaction requires. Feel free to keep this as a shared_ptr or convert to plain.

EXPECT_EQ(resulting_docs, nullptr);
EXPECT_NE(resulting_error, nullptr);
EXPECT_TRUE(resulting_docs.empty());
EXPECT_FALSE(resulting_status.ok());
Copy link
Member

Choose a reason for hiding this comment

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

This looks to be ported accurately, but we don't seem to be checking the error code here (other than ensuring it's not ok). Should we?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

/**
* Every time a document is read, this should be called to record its version.
* If we read two different versions of the same document, this will return an
* error through its out parameter. When the transaction is committed, the
Copy link
Member

Choose a reason for hiding this comment

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

"... this will return an error through its out parameter. ..."

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.

/**
* Returns the precondition for a document if the operation is an update,
* based on the provided UpdateOptions. Will return none precondition if an
* error occurred, in which case it sets the error parameter.
Copy link
Member

Choose a reason for hiding this comment

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

"... sets the error parameter. "

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.


/**
* Returns the precondition for a document if the operation is an update,
* based on the provided UpdateOptions. Will return none precondition if an
Copy link
Member

Choose a reason for hiding this comment

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

'UpdateOptions' is styled like a class, but I don't think any such class exists. (It's certainly not passed as a parameter here.) I suspect that's obsolete?

FWIW, the js repo uses this phrase: "Returns the precondition for a document if the operation is an update." (Though in the error case, it throws, so we'd still want to document what happens when an error occurs.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it looks obsolete. Done.


/**
* Returns the precondition for a document if the operation is an update,
* based on the provided UpdateOptions. Will return none precondition if an
Copy link
Member

Choose a reason for hiding this comment

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

'Will return none precondition...'

The implementation returns a failed status rather than a none precondition.

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. Thanks!

*/
util::Status last_write_error_;

std::unordered_map<model::DocumentKey,
Copy link
Member

Choose a reason for hiding this comment

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

FSTTransaction used a SortedMap

Technically, it uses a std::map, right? (Just to make sure we're looking at the same thing). According to our git history, this used to be a NSMutableDictionary. So I suspect the conversion from that to std::map was slightly wrong, and it should've been unsorted_map instead.

}

HARD_ASSERT(documents.size() == 1, "Mismatch in docs returned from document lookup.");
FSTMaybeDocument *internalDoc = documents.back();
Copy link
Member

Choose a reason for hiding this comment

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

It clearly makes no difference, but OOC, why back instead of front?

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 guess back seems to me as if it's more natural for a vector. Changed to front.

@rsgowman rsgowman assigned var-const and unassigned rsgowman Feb 11, 2019
Copy link
Contributor

@zxu123 zxu123 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 have some question on the ownership of Transaction.


NS_ASSUME_NONNULL_BEGIN

#pragma mark - FIRTransaction

@interface FIRTransaction ()

- (instancetype)initWithTransaction:(FSTTransaction *)transaction
- (instancetype)initWithTransaction:(std::shared_ptr<Transaction>)transaction
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually want to have shared ownership here? I expect the FIRTransaction will have sole ownership, maybe?

@@ -177,7 +178,9 @@ class RemoteStore : public TargetMetadataProvider,
void AddToWritePipeline(FSTMutationBatch* batch);

/** Returns a new transaction backed by this remote store. */
FSTTransaction* CreateTransaction();
// TODO(c++14): return a plain value when it becomes possible to move
Copy link
Contributor

Choose a reason for hiding this comment

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

Does each lambda has to retain an ownership of Transaction? I expect each lambda only use the passed in Transaction instead of retain an ownership of it. Whoever started a transaction would be the sole owner of the Transaction.

@zxu123 zxu123 removed their assignment Feb 11, 2019
@var-const var-const merged commit c422806 into master Feb 12, 2019
@var-const var-const deleted the varconst/fst-transaction3 branch February 12, 2019 00:14
bstpierr added a commit that referenced this pull request Feb 19, 2019
* master:
  E2e tests split (#2401)
  Split auth api tests into multiple cases (#2399)
  Add a ToString function to create human-readable debug descriptions (#2384)
  Split Auth unit tests (#2396)
  Create umbrella header for FIAM (#2392)
  Deprecate setMinimumSessionInterval (#2305)
  Forbid queries endAt an uncommitted server timestamp. (#2382)
  update changelog for release (#2383)
  Fix FIAM Travic CI issues (#2380)
  Remove swift sample for Auth (#2371)
  C++ migration: port `FSTTransaction` (#2362)
  Fix a bug where unlinking emailpassword doesn’t remove provider data (#2370)
  Fix a bug where sign in with Game Center doesn’t return additional user info (#2368)
  Firebase In-app messaging callbacks (#2354)
  Fix a bug where sign in with email link always return isNewUser as false (#2363)
  C++ migration: eliminate `FSTRemoteStore` (#2338)
@firebase firebase locked and limited conversation to collaborators Oct 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants