-
Notifications
You must be signed in to change notification settings - Fork 170
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
Throw if a table being written to has no subscriptions and FLX sync is enabled #5488
Conversation
b5b387c
to
2c58d3a
Compare
Wouldn't it be both simpler and faster to have something like |
@tgoyne - not sure I follow - SubscriptionSet is the thing that knows what tables have which subscriptions. That's like the whole point of that type. |
I don't understand how entirely new functionality can possibly be the point of a pre-existing type. The code which uses |
The point of the SubscriptionSet type is to hold and make it so you can query all the info about subscriptions, and by extension which tables they belong to. SubscriptionStore doesn't actually know/do anything with the data about subscriptions or tables or queries or anything, it just produces SubscriptionSets. Is your suggestion here that SubscriptionStore should have some special side-logic to lookup just the table names for the latest SubscriptionSet without actually creating a SubscriptionSet and returning that as a set? |
@tgoyne I've put up what I think you're suggesting. Is this what you had in mind? |
Yes. I think the primary advantage of that is it avoids introducing mutable state with additional invariants which have to be maintained, particularly when the mutable version isn't directly relevant (since we don't allow mixing updates to subscriptions and other writes). It'll also be faster, but you'd probably need an absurd number of subscriptions for that to be a consideration even with it being something that has to happen on every write transaction, so that's not a real consideration. (The "for_latest" part in |
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 wasn't involved in the discussion around this, but it seems like a very disruptive change in functionality. If the server processes changes, the objects modified outside subscriptions are automatically reverted, but with this check in place on the client side, all changes in the same commit are lost. It seems like it could be a good feature to have on during development though, what do you think about making this configurable so devs can turn it off for production apps?
src/realm/group.hpp
Outdated
return table_name.substr(g_class_name_prefix_len); | ||
} | ||
|
||
static bool table_name_is_class_name(StringData table_name) |
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.
my preference would be to overload table_is_public
since they do the same thing but for different types
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.
auto sub_mgr = weak_sub_mgr.lock(); | ||
if (!sub_mgr) { | ||
throw std::runtime_error("Subscription store was destroyed while user writes were on-going"); | ||
} |
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.
Why not move this weak -> strong pointer logic into the UniqueFunction? Then we can reduce the scope of strong pointers floating around.
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 turns out we don't need to capture the sub_mgr at all. I think we still want to lock it and get the info out of it here so that we can cache the result across all the writes in this transaction.
The scope of this work was defined and approved and signed off on in RPM-272. I think making this so you can control whether this validation is on/off within your Realm config would be a significant change in the agreed-upon scope. Also this apparently was enough of a footgun for developers during the beta period that just making it an error was decided to be worth it. cc @ianpward |
Fair enough. But it does look like that decision was made when the consequence of this type of write was a client reset. I imagine it is much less of a problem with the advent of compensating writes. |
I don't really have an opinion on if this needs an option, but adding an option would be trivial (just add |
Compensating writes was planned and in-progress when this feature was scoped. The idea from the scoping discussion was that this behavior is always a result of a programmer error and that it was very confusing for new users who would start doing writes and nothing would happen or they'd get an error message back asynchronously. From what I recall, it came up during user interviews @ianpward did. It was decided to just make the case where you're writing to a table without any subscriptions always an error. I was not the person pushing for this, so maybe @ianpward or @jsflax can correct me if I'm wrong about the history of this feature. |
Yes - we have had a clear and consistent feedback not only from our users, but shockingly our own realm engineers ran into this several times during the hackathon week. |
Ok makes sense, I'm fine with this going ahead. We can always adjust later if needed. I will review again once the test failures are fixed. |
9b05a5a
to
c56cfae
Compare
@ironage , I think the failures here are not caused by this work, can you review? |
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.
👍
Could use a note in the change log.
|
||
auto latest_sub_tables = sub_mgr->get_tables_for_latest(tr); | ||
return [tables = std::move(latest_sub_tables)](const Table& table) { | ||
if (table.is_embedded()) { |
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.
heads up @danieltabacaru: we're going to need a similar early out for asymmetric tables depending on which PR gets merged first.
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.
@jbreams since my pr went in first, you'll have to add the check
@@ -447,12 +447,21 @@ class ClientReplication final : public SyncReplication { | |||
{ | |||
} | |||
|
|||
// A write validator factory takes a read-able transaction and returns a UniqueFunction containing a | |||
// SyncReplication::WriteValidator. The factory will get called at the start of a write transaction | |||
// and the WriteValidator it returns will be re-used for all mutations wit`hin 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.
// and the WriteValidator it returns will be re-used for all mutations wit`hin the transaction. | |
// and the WriteValidator it returns will be re-used for all mutations within 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.
fixed.
@@ -102,6 +103,30 @@ void RealmCoordinator::create_sync_session() | |||
return; | |||
|
|||
m_sync_session = m_config.sync_config->user->sync_manager()->get_session(m_db, *m_config.sync_config); | |||
if (m_config.sync_config && m_config.sync_config->flx_sync_requested) { | |||
std::weak_ptr<sync::SubscriptionStore> weak_sub_mgr(m_sync_session->get_flx_subscription_store()); | |||
sync::ClientReplication& history = static_cast<sync::ClientReplication&>(*m_db->get_replication()); |
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.
auto?
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.
fixed
@@ -447,12 +447,21 @@ class ClientReplication final : public SyncReplication { | |||
{ | |||
} | |||
|
|||
// A write validator factory takes a read-able transaction and returns a UniqueFunction containing a |
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 sounds it is only used with a write transaction, right?
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.
changed this to say "write 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.
LGTM
ccf398a
to
88087e1
Compare
What, How & Why?
This is one of the things we committed to for World in the FLX sync adaptive changes scope.
It seems an easy footgun for users is to do writes to a table where they haven't set any permissions yet. This adds a validation hook to the ClientReplication class where we can just check the latest subscription set for whether it has a subscription for the table being written to. It doesn't validate anything about the query - compensating writes on the server will do stricter validation than on the client.
☑️ ToDos