-
Notifications
You must be signed in to change notification settings - Fork 7
Implement sync client #70
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
base: main
Are you sure you want to change the base?
Conversation
756f514
to
2ba3209
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.
I'm reading through the process flow and associated state management now, and just want to check that I understand it correctly:
- A
SqlController
withSyncClient
is created per connection when the extension is registered. This keeps aClientState
, which isIdle
/empty, until "start" even is received. - When the "start" even is received, it initializes a
SyncIterationHandle
, stored n theClientState
, and containing aStreamingSyncIteration
. A mutex on theClientState
protects against instantiating multiple copies of this at the same time. (Is this correct? Not sure if I understand the purpose of the "Mutex" here correctly). Once a "stop" event is received, this is disposed. - The
StreamingSyncIteration
contains aStorageAdapter
, which includes some prepared statements. These are all disposed automatically when theStreamingSyncIteration
is disposed via the "stop" event.
It seems like this gives us a nice way to persist prepared statements, instead of re-creating them every time. I've never measured the performance overhead, but I imagine there are places where persisting the prepared statements could reduce the performance overhead quite a bit. One disadvantage being that we'd still need to cater for the older APIs, which don't have a nice place to persist those statements. Either way, that is not important for the initial iteration here - we can do those optimizations later.
Exactly.
TLDR: There's only a single It's a bit of a trick to make I think I'll remove the mutex logic, it's fairly complex and doesn't provide any advantage for us here.
Yes.
FWIW it's kind of important that the statements don't outlive the sync iteration itself. SQLite doesn't let you close connections with pending prepared statements, so we need to give SDKs an opportunity to release resources in a clean manner. |
eb13fb5
to
4fe042d
Compare
This implements the sync client logic in the core extension. Client SDKs will still be responsible for opening sockets to the sync service, but the core extension is now responsible for driving that logic. The central interface here is the
powersync_control
SQL function, which is invoked by clients for a request to start a sync stream or lines received by the sync service. In response, the core extension responds with a list of instruction for the SDK (like e.g. updating the sync status).We expect that implementing the stream client here will:
The most important are structured like this:
core/src/bson
contains a no-std implementation of the BSON format with serde support. Thebson
crate unfortunately only supports std environments and I couldn't find a decent alternative. We don't support all BSON types, but that should be fine since we only need the types supported by JSON (as well as byte arrays).core/src/sync
contains logic to implement the sync client:line.rs
defines the sync lines as structs supporting deserialization.storage_adapter.rs
is used as an interface for some common database operations. It's a bit simpler than theBucketStorage
interface in the SDK since we can mostly just call other core functions directly without going through SQL.sync_status.rs
implements theSyncStatus
interface from our other SDKs and logic to update it along with sending update notifications to the client.interface.rs
defines thepowersync_control
SQL function driving the sync client.streaming_sync.rs
contains the actual state machine for the sync client. I've implemented it as an async coroutine that is polled every timepowersync_control
is called, which allows an implementation that looks similar to the ones across our other SDKs.