Add Sync bound to StreamResolver trait object in execute() #302

Closed
opened 2026-02-21 20:42:22 -08:00 by jwilger · 1 comment
jwilger commented 2026-02-21 20:42:22 -08:00 (Migrated from github.com)

Problem

The future returned by eventcore::execute() is !Send because stream_resolver() returns Option<&dyn StreamResolver<State>> where the StreamResolver trait object lacks a Sync bound. This means the dyn StreamResolver<State> reference held across .await points makes the overall future !Send.

This is a problem in async frameworks that require Send futures. For example, Leptos's #[server] macro (and Axum handlers in general) require the future to be Send. The compiler error looks like:

error[E0277]: `dyn StreamResolver<CreateProjectState>` cannot be shared between threads safely

Current Workaround

Callers must wrap eventcore::execute() in tokio::task::spawn_blocking + Handle::block_on to run the !Send future on a blocking thread:

let handle = tokio::runtime::Handle::current();
let result = tokio::task::spawn_blocking(move || {
    handle.block_on(eventcore::execute(store, command, RetryPolicy::new()))
})
.await
.expect("spawn_blocking task should not panic")?;

This works but adds unnecessary complexity and a thread-pool hop for every command execution.

Proposed Fix

Add a Sync bound to the StreamResolver trait object in the execute() function (or wherever the trait object is constructed). Since StreamResolver implementations are typically stateless or use Arc-wrapped state, requiring Sync should not be a breaking change in practice.

This would make the future from execute() Send, allowing direct use in async contexts without the spawn_blocking workaround.

Context

Discovered while integrating eventcore 0.5.0 into a Leptos + Axum application (stochastic_macro PR #186).

## Problem The future returned by `eventcore::execute()` is `!Send` because `stream_resolver()` returns `Option<&dyn StreamResolver<State>>` where the `StreamResolver` trait object lacks a `Sync` bound. This means the `dyn StreamResolver<State>` reference held across `.await` points makes the overall future `!Send`. This is a problem in async frameworks that require `Send` futures. For example, Leptos's `#[server]` macro (and Axum handlers in general) require the future to be `Send`. The compiler error looks like: ``` error[E0277]: `dyn StreamResolver<CreateProjectState>` cannot be shared between threads safely ``` ## Current Workaround Callers must wrap `eventcore::execute()` in `tokio::task::spawn_blocking` + `Handle::block_on` to run the `!Send` future on a blocking thread: ```rust let handle = tokio::runtime::Handle::current(); let result = tokio::task::spawn_blocking(move || { handle.block_on(eventcore::execute(store, command, RetryPolicy::new())) }) .await .expect("spawn_blocking task should not panic")?; ``` This works but adds unnecessary complexity and a thread-pool hop for every command execution. ## Proposed Fix Add a `Sync` bound to the `StreamResolver` trait object in the `execute()` function (or wherever the trait object is constructed). Since `StreamResolver` implementations are typically stateless or use `Arc`-wrapped state, requiring `Sync` should not be a breaking change in practice. This would make the future from `execute()` `Send`, allowing direct use in async contexts without the `spawn_blocking` workaround. ## Context Discovered while integrating eventcore 0.5.0 into a Leptos + Axum application (stochastic_macro PR #186).
jwilger-ai-bot commented 2026-04-11 17:15:04 -07:00 (Migrated from github.com)

Completed in #332 — added Send+Sync bounds to CommandLogic.

Completed in #332 — added Send+Sync bounds to CommandLogic.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jwilger/eventcore#302
No description provided.