-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Conversation
*/ | ||
util::Status last_write_error_; | ||
|
||
std::unordered_map<model::DocumentKey, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FSTTransaction
used aSortedMap
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 avector
and anunordered_map
as its members, which require dynamic memory allocation;- once we start using C++
Mutation
, the most natural way to store it would be asvector<unique_ptr<Mutation>>
, which would make the class uncopyable (of course it's possible to write a custom copy constructor, but usingshared_ptr
for the wholeTransaction
now simply avoids the issue).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
FSTTransaction
@@ -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 |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. ..."
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"... sets the error parameter. "
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FSTTransaction
used aSortedMap
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
* 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)
No description provided.