Background
Most of the sync bridge is already unnecessary. The core API is synchronous on both ends — AimDb::produce() / subscribe() are sync (aimdb-core/src/builder.rs:946, :963), Producer::try_produce is non-blocking, and the buffer reader exposes sync try_recv() alongside async recv() (aimdb-core/src/buffer/traits.rs:179, :189).
| Operation |
Status in core |
Bridge needed? |
| Produce / non-blocking produce |
db.produce / try_produce sync |
No — call directly |
| Non-blocking consume |
reader.try_recv() sync |
No — poll directly |
| Blocking consume |
only reader.recv().await |
Yes — the only runtime-specific piece |
So this is simplification, not re-architecture: lean on the sync API, isolate one small seam.
The block_on seam
aimdb-executor::RuntimeOps has no block_on/spawn (aimdb-executor/src/ops.rs:51), so the seam is net-new — but it lives in aimdb-sync, not widened into RuntimeOps.
- Only
block_on is needed, never spawn: producer and non-blocking paths call the sync API directly; blocking consume blocks the calling thread.
- The
std / embassy features are mutually exclusive per build, so the waiter is a concrete #[cfg]-selected type, not a trait object — no object-safety or generic-dispatch problem.
#[cfg(feature = "std")] → a waiter holding the tokio::runtime::Handle:
get() → handle.block_on(reader.recv())
get_with_timeout(d) → handle.block_on(async { tokio::time::timeout(d, reader.recv()).await }). The timed variant must go through the runtime so the tokio timer is in context — this is why a naive futures::block_on will not do.
Handle::block_on is called from the user's sync thread, never from inside the runtime, so this is valid.
Task
What this does not remove
- The DB's async stages (sources, transforms, taps, connectors) still need an executor driving
runner.run(). On std that stays the existing thread.
- Blocking consume still needs a real park/wake primitive per runtime. The seam hides it, it does not eliminate it.
Verification
cargo test -p aimdb-sync
make check
The blocking-API characterization tests must pass unchanged — they are written against the public API precisely so they can arbitrate this rewrite. examples/sync-api-demo behaves identically.
Files
aimdb-sync/src/{handle,producer,consumer}.rs
- Reference:
aimdb-core/src/builder.rs:946/:963, aimdb-core/src/buffer/traits.rs:189, aimdb-executor/src/ops.rs:51
Background
Most of the sync bridge is already unnecessary. The core API is synchronous on both ends —
AimDb::produce()/subscribe()are sync (aimdb-core/src/builder.rs:946,:963),Producer::try_produceis non-blocking, and the buffer reader exposes synctry_recv()alongside asyncrecv()(aimdb-core/src/buffer/traits.rs:179,:189).db.produce/try_producesyncreader.try_recv()syncreader.recv().awaitSo this is simplification, not re-architecture: lean on the sync API, isolate one small seam.
The
block_onseamaimdb-executor::RuntimeOpshas noblock_on/spawn(aimdb-executor/src/ops.rs:51), so the seam is net-new — but it lives inaimdb-sync, not widened intoRuntimeOps.block_onis needed, neverspawn: producer and non-blocking paths call the sync API directly; blocking consume blocks the calling thread.std/embassyfeatures are mutually exclusive per build, so the waiter is a concrete#[cfg]-selected type, not a trait object — no object-safety or generic-dispatch problem.#[cfg(feature = "std")]→ a waiter holding thetokio::runtime::Handle:get()→handle.block_on(reader.recv())get_with_timeout(d)→handle.block_on(async { tokio::time::timeout(d, reader.recv()).await }). The timed variant must go through the runtime so the tokio timer is in context — this is why a naivefutures::block_onwill not do.Handle::block_onis called from the user's sync thread, never from inside the runtime, so this is valid.Task
block_onseam as a concrete#[cfg(feature = "std")]waiter holding theHandle.SyncProducerto calldb.produce/Producer::try_producedirectly — drop thetokio::mpsc/oneshotround-trip, the forwarder task, and the stored runtime handle for produce.SyncConsumerto hold theBox<dyn BufferReader<T> + Send>directly and use the seam forget()/get_with_timeout();try_get()→reader.try_recv(). Delete the per-consumer forwarder task and thestd::sync::mpsc::sync_channel.detach/Droplifecycle — it still hosts the executor drivingrunner.run().unsafe impl Send/Sync for AimDbHandle(handle.rs:653) against the much smaller surface. If the justification no longer holds, or is no longer needed, say so explicitly in the PR.What this does not remove
runner.run(). Onstdthat stays the existing thread.Verification
cargo test -p aimdb-sync make checkThe blocking-API characterization tests must pass unchanged — they are written against the public API precisely so they can arbitrate this rewrite.
examples/sync-api-demobehaves identically.Files
aimdb-sync/src/{handle,producer,consumer}.rsaimdb-core/src/builder.rs:946/:963,aimdb-core/src/buffer/traits.rs:189,aimdb-executor/src/ops.rs:51