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

#15321: Only one tab writes to the DB #382

Merged
merged 44 commits into from
Dec 1, 2023

Conversation

BeeMargarida
Copy link
Contributor

@BeeMargarida BeeMargarida commented Oct 3, 2023

Details

This is the 1st part solution to fix the problem of the linked issue. The links to the broader discussion are below. This PR is big because it contains a RN app that contains an example for the e2e tests.

The goal of this PR is to change Onyx execution so that when there are several tabs open, only one of them can write to the DB. This is done by defining a leader tab, and all other tabs defer their writes to this one leader.

Changes included in this PR:

  • All writes to the DB are made by the leader tab - this involves using the BroadcastAPI to communicate each set, multiSet, merge and clear to the leader tab.
  • Ported ActiveClientManager to react-native-onyx and used BroadcastAPI instead of the indexedDB to reach a consensus between tabs about who is the leader (the last tab to be open). It uses timestamps in the messages to check that the last tab to send the message is the leader. If for a change the timestamps are the same, the client ID of the tab is used as a tiebreaker.
  • When the leader tab is closed, a new consensus is reached about the new leader tab. The old leader tab communicates that it is closing so that the others can try to reach a consensus.
  • The leader tab is listening to all write requests from the other tabs and execute them once they are received.
  • The leader tab does not execute writes to the DB while clear is running. These writes are ignored while the clear is running
  • The callback for the Onyx.clear is communicated. This is necessary to allow actions to execute after clear, so a non-leader needs a way of executing those actions without having access to the promise. So, a new Onyx.onClear should be set with the callback to execute after the Onyx.clear completes.

The GH comment with the explanation: Expensify/App#15321 (comment)

The doc with the discussion: https://docs.google.com/document/d/1Psr4Q4A2uH2b7l9CKBuB_dBT-15V4lJ7SOaH4uQOve4/edit

Related Issues

GH_LINK: Expensify/App#15321

Automated Tests

Added e2e tests using playwright. To run them:

  • npm install (should install onyx dependencies and the e2e app as well)
  • npm run e2e or npm run e2e-ui to run the e2e tests

Current result:

Screenshot 2023-10-03 at 11-05-00 Playwright Test Report

Linked PRs

This will require future changes in newDot, since ActiveClientManager is now available in react-native-onyx.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2023

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@BeeMargarida
Copy link
Contributor Author

I have read the CLA Document and I hereby sign the CLA

lib/ActiveClientManager/index.web.js Outdated Show resolved Hide resolved
lib/ActiveClientManager/index.web.js Outdated Show resolved Hide resolved
lib/Onyx.js Show resolved Hide resolved
lib/broadcast/index.native.js Show resolved Hide resolved
lib/storage/WebStorage.js Outdated Show resolved Hide resolved
tests/e2e/app/package.json Show resolved Hide resolved
tests/e2e/simple.spec.ts Outdated Show resolved Hide resolved
package.json Show resolved Hide resolved
lib/Str.js Show resolved Hide resolved
Copy link

@rezkiy37 rezkiy37 left a comment

Choose a reason for hiding this comment

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

Left comments. However, in general, great job!

lib/ActiveClientManager/index.web.js Show resolved Hide resolved
lib/ActiveClientManager/index.web.js Show resolved Hide resolved
lib/Onyx.js Outdated Show resolved Hide resolved
lib/Onyx.js Outdated Show resolved Hide resolved
lib/ActiveClientManager/index.d.ts Outdated Show resolved Hide resolved
tests/e2e/app/src/App.js Outdated Show resolved Hide resolved
tests/e2e/app/src/Main.js Outdated Show resolved Hide resolved
tests/e2e/app/src/Main.js Outdated Show resolved Hide resolved
tests/e2e/app/src/Main.js Show resolved Hide resolved
tests/e2e/app/src/Main.js Show resolved Hide resolved
@roryabraham
Copy link
Contributor

@koko57 if you have questions or need clarification on any of my suggestions just let me know 🙂

@koko57
Copy link
Contributor

koko57 commented Nov 8, 2023

@roryabraham Sorry, I had to take care of my other issues, but I'll address the rest of the comments soon

@roryabraham
Copy link
Contributor

@koko57 thanks for pushing this forward. Please ping me when it's ready for review

@koko57
Copy link
Contributor

koko57 commented Nov 15, 2023

@roryabraham I think it's ready for re-review. On separate channels I commented here #382 (comment) - do we really need it? For the unsubscribe logic - as we no longer need to subscribe so many times, I also think it's unnecessary: #382 (comment). Let me know what do you think about it 🙂

Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

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

I still think we need to add some unsubscribe logic. Even if there are fewer subscriptions, we're never cleaning them up which means we're propagating data to tabs that no longer exist unnecessarily.

.github/workflows/e2e.yml Outdated Show resolved Hide resolved
lib/ActiveClientManager/index.web.js Outdated Show resolved Hide resolved
lib/ActiveClientManager/index.web.js Outdated Show resolved Hide resolved
lib/ActiveClientManager/index.web.js Outdated Show resolved Hide resolved
@koko57
Copy link
Contributor

koko57 commented Nov 27, 2023

I still think we need to add some unsubscribe logic. Even if there are fewer subscriptions, we're never cleaning them up which means we're propagating data to tabs that no longer exist unnecessarily.

@roryabraham I checked it once again and if I understand this logic correctly, after closing tabs we no longer propagate the data to them. subscriptions array is separate for each tab and contains two callbacks - the one that we push while subscribing to in Onyx.init() and the second one in ActiveClientManager.init(). Both callbacks are then fired when a message from the channel is received, message object is passed to them both but they are looking for and handling different data types. The one from Onyx.init() (subscribeToEvents()) for data types to write to Onyx, the one from ActivveClientManager.init() for new tab selection. And we want these both callbacks for each tab. The part of code I removed ActiveClientManager.subscribeToClientChange(() => { subscribeToEvents(); (comment) added the callback from subscribeToEvents() so if we had i.e 8 tabs open the first one opened had one callback listening for leader change and 8 for listening for writing to Onyx. The second one had one callback less and so on and the leader tab had only these two different callbacks I mentioned earlier. After removing this part every tab has only these two callbacks from being open to being closed, no matter if we open a new tab or not. Each tab has its own subscriptions array, they don't share this array or store the info about subscriptions for other tabs, they are only communicating via sending/receiving messages through the broadcast.
I only wonder if we should clear the whole array and/or disconnect from the channel in 'beforeunload' but when a BroadcastChannel instance is closed (when closing the tab) event listeners (onmessage) should be removed as part of the cleanup process.

Let me know what do you think about it and correct me if I'm wrong with the logic 🙂

@@ -0,0 +1,99 @@
/**
* When you have many tabs in one browser, the data of Onyx is shared between all of them. Since we persist write requests in Onyx, we need to ensure that
Copy link
Contributor

Choose a reason for hiding this comment

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

Just realizing that this comment is a bit out-of-date. This code no longer has anything to do with processing write requests.

@roryabraham roryabraham merged commit d858361 into Expensify:main Dec 1, 2023
4 checks passed
@roryabraham
Copy link
Contributor

Let's follow up ASAP with a PR to upgrade Onyx in E/App

@koko57
Copy link
Contributor

koko57 commented Dec 4, 2023

@roryabraham While trying to bump Onyx version in Expensify App I got this error
Screenshot 2023-12-04 at 11 00 58

I've opened a draft PR for this - let me know if we can fix it this way

@koko57 koko57 mentioned this pull request Dec 4, 2023
@koko57
Copy link
Contributor

koko57 commented Dec 8, 2023

@roryabraham wdyt?

@roryabraham
Copy link
Contributor

Approved #434

@chrispader
Copy link
Contributor

chrispader commented Jan 20, 2024

This PR caused a regression after the Onyx bump.

Fixing PR created here: #455

chrispader pushed a commit to margelo/react-native-onyx that referenced this pull request Jan 23, 2024
…eader_write"

This reverts commit d858361, reversing
changes made to d76bed7.
@chrispader chrispader mentioned this pull request Jan 23, 2024
42 tasks
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.

7 participants