-
Notifications
You must be signed in to change notification settings - Fork 932
Factor out Multi-Tab code #2882
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
Conversation
bf2097f
to
7178633
Compare
Binary Size ReportAffected SDKs
Test Logs |
ace33a9
to
b7766c0
Compare
Note: Some of the unit tests are failing after Master merge. I will fix them later tonight. |
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.
Below is my feedback for multi-tab.
Along the way I found a small issue in the Firestore
class. The constructor takes a persistenceProvider
parameter and I think you meant this to be named componentProvider
.
@@ -210,6 +197,68 @@ export class MemoryLruComponentProvider extends MemoryComponentProvider { | |||
* Provides all components needed for Firestore with IndexedDB persistence. | |||
*/ | |||
export class IndexedDbComponentProvider extends MemoryComponentProvider { |
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'm unable to comment on lines of code outside of what's changed however I see a few high level things:
MemoryComponentProvider
should explicitly implicitly implementComponentProvider
. Currently it doesn't.- I think these assertions provide the correct guarantee with minimal lines of code but I wonder if instead you modified the
ComponentProvider
interface to take generic arguments for every member with defaults and then overrode it inMemoryComponentProvider
and again here it would get you compile time safety and let you eliminate these assertions. It would be a large list of generic constraints which comes with its own problems but might be worth thinking about.
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 (also renamed the constructor argument in FirestoreClient).
- I opted not to go down the route and make this generic, but I did overwrite some of the members and use more explicit types. This gets rid of some of the asserts. Let me know if that is enough.
As part of the Master merge, I also had to fix the spec tests to be aware of the differences between the different persistence implementations.
import { LocalStore } from '../local/local_store'; | ||
import { SyncEngine } from './sync_engine'; | ||
import { LocalStore, MultiTabLocalStore } from '../local/local_store'; | ||
import { MultiTabSyncEngine, SyncEngine } from './sync_engine'; |
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 review doesn't include MultiTabSyncEngine
but I have some thoughts on how to move SharedClientState
and related properties into it. Most of these options will make the code ugly, awkward or might not be viable but want to throw it out there anyway.
In any method in SyncEngine
that has multi-tab logic is it possible to refactor it to not be in the middle? For example removeAndCleanupTarget()
only has one line at the top. Could you do that to everything else? If so, you could rely on method overloading in MultiTabSyncEngine
to do pre/post processing and call the base class method in the appropriate place. If the logic can't be split it might be worth overloading the method entirely and relying on unit tests to ensure consistency.
Alternatively, in methods where you need a return value from SharedClientState
you can modify the method to have an optional callback parameter that usually gives you a default value but in MultiTabSyncEngine
you can overload the method and call into the base with an argument for the callback that gives you a real result. You can do the same for notification only methods OR raise an event that MultiTabSyncEngine
can subscribe to. The latter is super weird but eliminates the reference entirely without needing a stub implementation.
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 looked through all the calls to SharedClientState to see if we can improve the logic and remove some of the tight coupling. Unfortunately, I think in almost all cases the current logic is simpler than what we would have if we pulled out the code:
- If we overwrite the methods to extract one line, it creates an interaction model that is much harder to follow. While it is relatively straightforward to pull out the first or last line in some of these methods, it essentially hides these lines from view if the use is not aware that they are overwritten in
MultiTabSyncEngine
. - In some cases (like
removeAndCleanupTarget()
) this is not actually all that bad - but it creates some inconsistencies between target allocation and target cleanup. I would rather keep all of this code in one place.
I did create an overwrite for applyOnlineStateChange
. This method has different behavior for Multi-Tab clients and has no "mirrored" behavior in SyncEngine.
6678110
to
7835818
Compare
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.
Thanks for the review! I applied some of your suggestions and replied to your comments when I decided not to. To truly refactor out all Mutli-Tab code, the code flow becomes even more convoluted than it is, so at the moment I prefer to only refactor methods where we don't have to drastically alter the code flow.
I did experiment with creating a stand alone MultiTabComponentProvider. This is much cleaner, but we unfortunately don't know whether the user would like to opt into Multi-Tab persistence until the client has been initialized and cannot register the specific provider at compile time.
import { LocalStore } from '../local/local_store'; | ||
import { SyncEngine } from './sync_engine'; | ||
import { LocalStore, MultiTabLocalStore } from '../local/local_store'; | ||
import { MultiTabSyncEngine, SyncEngine } from './sync_engine'; |
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 looked through all the calls to SharedClientState to see if we can improve the logic and remove some of the tight coupling. Unfortunately, I think in almost all cases the current logic is simpler than what we would have if we pulled out the code:
- If we overwrite the methods to extract one line, it creates an interaction model that is much harder to follow. While it is relatively straightforward to pull out the first or last line in some of these methods, it essentially hides these lines from view if the use is not aware that they are overwritten in
MultiTabSyncEngine
. - In some cases (like
removeAndCleanupTarget()
) this is not actually all that bad - but it creates some inconsistencies between target allocation and target cleanup. I would rather keep all of this code in one place.
I did create an overwrite for applyOnlineStateChange
. This method has different behavior for Multi-Tab clients and has no "mirrored" behavior in SyncEngine.
@@ -210,6 +197,68 @@ export class MemoryLruComponentProvider extends MemoryComponentProvider { | |||
* Provides all components needed for Firestore with IndexedDB persistence. | |||
*/ | |||
export class IndexedDbComponentProvider extends MemoryComponentProvider { |
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 (also renamed the constructor argument in FirestoreClient).
- I opted not to go down the route and make this generic, but I did overwrite some of the members and use more explicit types. This gets rid of some of the asserts. Let me know if that is enough.
As part of the Master merge, I also had to fix the spec tests to be aware of the differences between the different persistence implementations.
7eeeeb5
to
86ee9fa
Compare
I seem to have fixed all breakages. This is again ready for review. |
constructor(private readonly queryEngine: QueryEngine) {} | ||
constructor( | ||
private readonly queryEngine: QueryEngine, | ||
readonly type: 'index-free' | 'simple' |
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.
Would it make sense to create a type definition for this?
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 added a test-only enum.
allowTabSynchronization: multiClient, | ||
persistenceKey: TEST_PERSISTENCE_PREFIX, | ||
const persistence = new IndexedDbPersistence( | ||
/* allowTabSynchronization= */ multiClient, |
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.
Optional: Consider including a comment for sequenceNumberSyncer
and persistenceKey
or dropping this one. The inconsistency seems strange.
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 goal of these comments is to make the argument type descriptive when just the value is not descriptive by default. In this case, one could argue that multiClient
alone is descriptive enough. Dropped.
allowTabSynchronization: !!options.synchronizeTabs, | ||
persistenceKey: TEST_PERSISTENCE_PREFIX, | ||
const persistence = new IndexedDbPersistence( | ||
!!options.synchronizeTabs, |
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 is another instance of my previous comment. You should comment this consistently.
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 did not add the inline argument name here since I deemed synchronizeTabs
more descriptive than multiTab
. As stated above, both are probably descriptive enough on their own.
91b1a02
to
ba98a20
Compare
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
This PR is based on the changes in #2828 (and the reason for #2828) and meant to reduce the size of the memory-only build.
It creates two new classes:
MultiTabLocalStore
andMultiTabSyncEngine
that each inherit from their old structures. It then moves all fully-encapsulated Multi-Tab only code the new classes which are only used in the full Firestore build.Since
MultiTabLocalStore
only works withIndexedDbPersistence
, this change also allows us to remove a few methods fromMemoryPersistence
that were never used. I did this by removing the methods from the shared interfaces and moving the method comment down to the IndexedDbBimplementation.As a third thing, it changes
LocalStoreTester
.LocalStoreTester
needs to know the source of theQueryEngine
(whether index-free or not), but that plumbing is pretty gnarly, so I opted for the pattern used in C++ and added atype
: https://github.com/firebase/firebase-ios-sdk/blob/d9b7a33f0c0464e72e894aa7c207efc07dadbbbe/Firestore/core/test/firebase/firestore/local/counting_query_engine.h#L62