-
Notifications
You must be signed in to change notification settings - Fork 61
OPFS Multiple Tab Trigger Invocation #804
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
Open
stevensJourney
wants to merge
13
commits into
main
Choose a base branch
from
multiple-tab-triggers-opfs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
🦋 Changeset detectedLatest commit: 5ea40a2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview
Fixes:
Our SQLite trigger helpers currently allow for tracking INSERT, UPDATE, and DELETE operations on a PowerSync view. These operations are stored in a SQLite table for each managed consumer to process.
Our managed triggers currently only support temporary SQLite triggers, where diff operations are stored in temporary SQLite tables. This approach avoids extra storage overhead for diff records and simplifies cleanup. The temporary nature means the triggers and tables are scoped to the SQLite connection and are disposed automatically once the connection is closed.
The Problem
For OPFS (in Web), we create a separate SQLite connection per tab. Triggers are also scoped to each tab, so each tab creates its own managed trigger (hence its own SQLite trigger and destination table). Having multiple connections across multiple tabs causes issues:
A mutation performed by one connection does not trigger a temporary trigger defined for another connection.
This causes our trigger consumers to miss updates from other tabs.
The Solution
Persisted SQLite triggers are stored in the database. A mutation from any connection will trigger all persisted triggers, ensuring that mutations from other tabs are written to the destination table of other tabs' managed triggers.
This PR adds the ability to optionally use persisted SQLite tables and triggers (enabled by default for OPFS connections). While these resources do use persistence, they are still temporary in nature. The PowerSync SDK will dispose of these resources automatically when they are detected as no longer in use.
Implementation
Using persisted triggers and destination tables solves the multiple SQLite connection problem, but introduces a new challenge: how do we dispose of the persisted tables and triggers when they are no longer needed?
The Claim Mechanism
Disposing stale items for closed tabs (and SQLite connections) is not straightforward. When a tab is closed, there is usually no reliable method for ensuring cleanup has completed successfully.
To address this, the implementation introduces a "claim" mechanism through a new
TriggerClaimManagerinterface. This interface provides two core capabilities: obtaining a claim on a resource (returning a release callback), and checking whether a resource is currently claimed.A heartbeat or time-to-live mechanism was also considered, but this approach seemed vulnerable to slow or frozen tab issues. A tab that's temporarily unresponsive could have its resources incorrectly cleaned up. The hold method avoids this problem since a hold remains valid regardless of how responsive the owning tab is.
The resources and claims to resources are freed if the trigger is disposed using the callback returned from the method which created it.
For cases where the triggers are not explicitly disposed, a cleanup process is implemented. This process runs whenever a new
PowerSyncDatabaseis created and at a 2 minute interval.A unique UUID is associated with each managed trigger. This ID and destination SQLite table are encoded into the names of the SQLite triggers created. The naming convention for triggers is as follows:
__ps_temp_trigger_${operation}__${destination_table}__${trigger_id}The cleanup process:
sqlite_mastertable for all triggers which match the__ps_temp_triggernaming convention. The destination table and trigger id are extractedTriggerHoldManagerif any active claims are present for the trigger idAlternative approaches were considered for the tracking of the managed trigger Ids and tables.
ps_kv: Cant be used since the keys inps_kvare cleared indisconnectAndClear`, which will discard the state.Platform-Specific Claim Managers
The claim mechanism needs to work differently depending on the platform.
For shared-memory SDKs like Node and React Native, a
MemoryTriggerClaimManageruses a global in-memory store to track claims. Since these environments typically share memory within a single process, this straightforward approach works well.For web environments, a
NavigatorTriggerClaimManagerleverages the Navigator Locks API to manage claims across browser tabs. This allows us to determine if any tab is still holding on to a trigger, even when those tabs can't directly communicate with each other.The web
PowerSyncDatabaseenables persistence by default for OPFS VFS modes, while keeping temporary triggers forIDBBatchAtomicVFSwhere multiple connections aren't an issue.