Skip to content

Conversation

@stevensJourney
Copy link
Contributor

Summary

This PR introduces a new DispatchStrategy API that allows users to customize how database operations are dispatched to coroutine contexts. By default, operations use Dispatchers.IO, but users can now provide a custom CoroutineDispatcher or a fully custom DispatchFunction for complete control over the execution context.

Motivation

The primary motivation for this feature is to support the Swift Connection Pool implementation, where the pool itself handles dispatching operations. In this case, we need to bypass PowerSync's default dispatching mechanism and let the pool manage the execution context.

Implementation Details

New Types

  1. DispatchFunction - An interface that defines how to dispatch operations:

    public interface DispatchFunction {
        public suspend operator fun <R> invoke(block: suspend () -> R): R
    }
    • Must be an interface (not a function type) because Kotlin doesn't support function types with generic type parameters
    • Uses operator invoke to enable convenient syntax: dispatchFunction { ... }
  2. DispatchStrategy - A sealed class with three variants:

    • Default: Uses Dispatchers.IO (the default behavior)
    • Dispatcher: Accepts a CoroutineDispatcher directly
    • Custom: Accepts a custom DispatchFunction for full control

Changes

  • Added dispatchStrategy parameter (defaults to DispatchStrategy.Default) to:
    • PowerSyncDatabase() factory function
    • PowerSyncDatabase.opened()
    • PowerSyncDatabase.openInMemory()
  • Updated InternalDatabaseImpl to accept a non-nullable DispatchStrategy and use it for all database operations
  • Refactored DispatchStrategy.Default to internally use Dispatcher(Dispatchers.IO) to avoid code duplication

Swift Connection Pool Integration

The Swift Connection Pool implementation uses DispatchStrategy.Custom with a no-op dispatch function, allowing the pool to handle dispatching:

dispatchStrategy = DispatchStrategy.Custom(
    object : DispatchFunction {
        override suspend fun <R> invoke(block: suspend () -> R): R {
            // We leave the dispatching up to the pool
            return block()
        }
    }
)

Breaking Changes

None - this is a fully backward compatible addition. The dispatchStrategy parameter has a default value of DispatchStrategy.Default, which maintains the existing behavior.

Copy link
Contributor

@simolus3 simolus3 left a comment

Choose a reason for hiding this comment

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

I like these changes, but I believe we may be able to simplify the API surface quite a bit by relying on interfaces already exposed by the standard library. Instead of introducing our own type for this, I think we can just use CoroutineContext here:

  1. I don't see the purpose of a DispatchStrategy.Default, we should just take a CoroutineContext parameter that defaults to Dispatchers.IO.
  2. Everything that is expressible as a DispatchFunction implementation would also be expressible as a ContinuationInterceptor, so I don't see the point of having it.

I believe replacing dispatchStrategy: DispatchStrategy with databaseContext: CoroutineContext and then doing withContext(databaseContext) should cover all our needs here.

Another benefit is that coroutine contexts are composable with the + operator, so they're likely more convenient to use than this.

Comment on lines +110 to +117
DispatchStrategy.Custom(
object : DispatchFunction {
override suspend fun <R> invoke(block: suspend () -> R): R {
// We leave the dispatching up to the pool
return block()
}
},
),
Copy link
Contributor

Choose a reason for hiding this comment

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

If we used CoroutineContext here, this could e.g. be an EmptyCoroutineContext.

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.

3 participants