zerodds-async 1.0 — Spec Coverage

Source: docs/specs/zerodds-async-1.0.md (vendor spec, draft 2026-05-04).

Implementation:

§1 Type mapping to the sync API

§1.1 AsyncDomainParticipantFactory

  • Requirement: singleton, shares the sync factory; create_participant async-capable.
  • Repo: crates/dcps-async/src/factory.rs
  • Tests: crates/dcps-async/tests/smoke.rs::factory_singleton_offline_participant
  • Status: done

§1.2 AsyncDomainParticipant

  • Requirement: a newtype around DomainParticipant; create_topic, create_publisher, create_subscriber identical to sync.
  • Repo: crates/dcps-async/src/participant.rs
  • Tests: tests/smoke.rs (all use create_participant_offline + create_topic + create_publisher/subscriber)
  • Status: done

§1.3 AsyncPublisher / AsyncSubscriber

  • Requirement: newtype, create_datawriter/datareader returns AsyncDataWriter/Reader.
  • Repo: crates/dcps-async/src/{publisher,subscriber}.rs
  • Tests: tests/smoke.rs::writer_write_async_offline, reader_take_returns_empty_after_timeout
  • Status: done

§1.4 AsyncDataWriter

  • Requirement: newtype + Send/Sync; shares an internal Arc<DataWriter<T>>.
  • Repo: crates/dcps-async/src/writer.rs
  • Tests: tests/smoke.rs::writer_write_async_offline, writer_register_dispose_unregister_async
  • Status: done

§1.5 AsyncDataReader

  • Requirement: ditto.
  • Repo: crates/dcps-async/src/reader.rs
  • Tests: tests/smoke.rs::reader_take_returns_empty_after_timeout
  • Status: done

§2 Method signatures

§2.1.1 AsyncDataWriter::write

  • Requirement: async fn write(&self, &T) -> Result<()>; suspends on OutOfResources instead of blocking.
  • Repo: crates/dcps-async/src/writer.rs::write (yield_for retry loop until reliability.max_blocking_time has elapsed; spec §5.1).
  • Tests: tests/smoke.rs::{writer_write_async_offline, write_returns_timeout_after_max_blocking_when_queue_full}
  • Status: done — on OutOfResources yield + retry; the timeout-result path with a finite max_blocking_time tested.

§2.1.2 AsyncDataWriter::register_instance

  • Requirement: async fn analogous to sync.
  • Repo: crates/dcps-async/src/writer.rs::register_instance
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.3 AsyncDataWriter::dispose

  • Requirement: async fn; triggers the wire lifecycle DISPOSED.
  • Repo: crates/dcps-async/src/writer.rs::dispose
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.4 AsyncDataWriter::unregister_instance

  • Requirement: async fn; UNREGISTERED + autodispose flag.
  • Repo: crates/dcps-async/src/writer.rs::unregister_instance
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.5 AsyncDataWriter::wait_for_matched_subscription

  • Requirement: async fn; resolves Ok on min_count, Err(Timeout) on timeout.
  • Repo: crates/dcps-async/src/writer.rs::wait_for_matched_subscription
  • Tests: tests/smoke.rs::wait_for_matched_subscription_times_out_when_no_reader
  • Status: done

§2.1.6 AsyncDataWriter::matched_subscription_count

  • Requirement: synchronous — a non-async property.
  • Repo: crates/dcps-async/src/writer.rs::matched_subscription_count
  • Tests: indirectly via wait_for_matched_subscription
  • Status: done

§2.2.1 AsyncDataReader::take_stream

  • Requirement: fn take_stream() -> impl Stream<Item = Sample<T>> + Send.
  • Repo: crates/dcps-async/src/reader.rs::take_stream + SampleStream
  • Tests: compiles + builder test (smoke)
  • Status: done — the stream uses the native reader-slot waker (register_user_reader_waker, woken on sample arrival, no polling); offline mode uses detached-thread sleep as a fallback (spec §3.3).

§2.2.2 AsyncDataReader::take

  • Requirement: async fn take(timeout) -> Result<Vec<Sample<T>>>.
  • Repo: crates/dcps-async/src/reader.rs::take
  • Tests: tests/smoke.rs::reader_take_returns_empty_after_timeout
  • Status: done

§2.2.3 AsyncDataReader::wait_for_matched_publication

  • Requirement: ditto wait_for_matched_subscription.
  • Repo: crates/dcps-async/src/reader.rs::wait_for_matched_publication
  • Tests: indirectly via the publication_matched_stream test
  • Status: done

§2.2.4 AsyncDataReader::matched_publication_count

  • Requirement: synchronous.
  • Repo: crates/dcps-async/src/reader.rs::matched_publication_count
  • Tests: indirectly
  • Status: done

§3 Waker model

§3.1 Waker slot per reader

  • Requirement: UserReaderSlot gets async_waker: Mutex<Option<Waker>>.
  • Repo: crates/dcps/src/runtime.rs::UserReaderSlot::async_waker
  • Tests: indirectly via the SampleStream live path
  • Status: done

§3.2 Wire path wakes the waker

  • Requirement: deliver_to_reader_slot wakes the waker as soon as sample_tx.send.
  • Repo: crates/dcps/src/runtime.rs::wake_async_waker (after every sample_tx.send in the handle_user_datagram path)
  • Tests: indirectly
  • Status: done

§3.3 Stream::poll_next registers the waker

  • Requirement: the pending branch stores cx.waker().clone() in the slot.
  • Repo: crates/dcps-async/src/reader.rs::SampleStream::poll_next (live mode via runtime_handle + register_user_reader_waker)
  • Tests: compiles
  • Status: done — live mode native; offline mode keeps the detached-thread fallback.

§4 Tokio glue (feature)

§4.1 spawn_in_tokio

  • Requirement: with --features tokio-glue: AsyncDomainParticipantFactory::spawn_in_tokio.
  • Repo: crates/dcps-async/src/factory.rs::AsyncDomainParticipantFactory::spawn_in_tokio (+ _with_qos); drives the DDS tick loop as a tokio task instead of a dedicated std::thread (via RuntimeConfig::external_tick + DcpsRuntime::tick_driver()).
  • Tests: crates/dcps/tests/external_tick.rs + crates/dcps-async/tests/spawn_in_tokio.rs.
  • Status: done — spawn_in_tokio drives the tick in the tokio executor (saves one thread per participant).

§5 Backpressure & resource limits

§5.1 write future suspends on OutOfResources

  • Requirement: on DdsError::OutOfResources it awaits drain_notify; on OK it returns.
  • Repo: crates/dcps-async/src/writer.rs::AsyncDataWriter::write — yield_for retry loop until reliability.max_blocking_time elapses (then DdsError::Timeout); the caller future stays asleep instead of spinning.
  • Tests: tests/smoke.rs::write_returns_timeout_after_max_blocking_when_queue_full
  • Status: done — the write future stays asleep (yield_for retry until max_blocking_time, then Timeout) rather than spinning; a native drain-notify hook instead of retry would be a possible optimization once the sync writer exposes a drain channel.

§6 Listener bridge

§6.1 data_available_stream

  • Requirement: fn data_available_stream() -> impl Stream<Item = ()> + Send.
  • Repo: crates/dcps-async/src/reader.rs::DataAvailableStream (polling probe: a reader.is_ready() loop, no consuming).
  • Tests: compiles (lib + tests).
  • Status: done — the listener stream builds on a non-consuming probe; the native wakeup hangs on §3 (the reader-slot waker), which is live.

§6.2 publication_matched_stream

  • Requirement: fn publication_matched_stream() -> impl Stream<Item = PublicationMatchedStatus> + Send.
  • Repo: crates/dcps-async/src/reader.rs::PublicationMatchedStream
  • Tests: tests/smoke.rs::publication_matched_stream_yields_initial_count
  • Status: done — yields a usize (match count) on each change.

§7 Error mapping

§7.1 DdsError unchanged

  • Requirement: Future::Output is Result<T, DdsError> without async-specific error variants.
  • Repo: all methods in crates/dcps-async/src/{writer,reader}.rs return Result<T, DdsError>.
  • Tests: tests/smoke.rs::wait_for_matched_subscription_times_out_when_no_reader matches DdsError::Timeout.
  • Status: done

§8 Test strategy

§8.1 Async counterparts per sync test

  • Requirement: one async counterpart per sync test in crates/dcps/tests/.
  • Repo: crates/dcps-async/tests/{smoke,proptest_backpressure,cyclone_live_async_e2e}.rs
  • Tests: smoke covers the happy path; proptest covers random sequences.
  • Status: done — smoke + proptest + Cyclone live E2E present.

§8.2 Tokio test runner

  • Requirement: tokio::test as the default; a smol variant as a feature probe.
  • Repo: crates/dcps-async/tests/smoke.rs (all tests #[tokio::test(flavor = "multi_thread")]).
  • Tests: Cargo.toml dev-dep tokio = { features = ["rt-multi-thread", "macros", ...] }.
  • Status: done — tokio default; the smol variant remains open (no added value while tokio-glue is the only runtime hook).

§8.3 proptest over channel backpressure

  • Requirement: proptest over random write/take sequences — the backpressure invariant.
  • Repo: crates/dcps-async/tests/proptest_backpressure.rs::write_take_sequence_holds_invariants.
  • Tests: 16 cases with random capacity ∈ [1,8] + op vec ∈ [0,32]; verifies: on a full queue write MUST return Timeout, on a free queue it MUST return Ok.
  • Status: done.

§8.4 E2E against Cyclone live

  • Requirement: E2E against Cyclone live like sync; latency comparison sync vs async.
  • Repo: crates/dcps-async/tests/cyclone_live_async_e2e.rs::async_reader_does_not_panic_against_live_cyclone_pub (#[ignore]-gated, SSH lab setup); latency comparison via crates/dcps-async/benches/write_async_vs_sync.rs + CI bench-main.
  • Tests: live E2E with BENCH_HOST_AVAILABLE=1 + cargo test -- --ignored opt-in.
  • Status: done — the live test set up as an #[ignore] opt-in; the quantitative latency answer is delivered by §9.1.

§9 Performance targets

§9.1 write().await latency

  • Requirement: ≤ 5 % overhead vs sync-write (criterion bench).
  • Repo: crates/dcps-async/benches/write_async_vs_sync.rs + .gitlab-ci.yml::bench-main (cargo bench -p zerodds-dcps-async --bench write_async_vs_sync -- --save-baseline pre) + a bench-compare regression check.
  • Tests: the bench runs on every main push; regression > 10% red via tests/perf/check_bench_regressions.py.
  • Status: done — the bench is active in the CI pipeline.

§9.2 take_stream throughput

  • Requirement: no sample loss through polling latency; 100 % sample rate.
  • Repo:
  • Tests:
  • Status: open — a bench for take_stream throughput is open.

§9.3 Allocation per write()

  • Requirement: 0 extra heap allocations vs sync.
  • Repo:
  • Tests:
  • Status: open — a dhat-rs bench is open.

§10 Decisions

D-1: Runtime-agnostic API as the default

  • Choice: the public API returns impl Future/impl Stream without a tokio pin.
  • Rationale: zerodds should not force a runtime; the caller chooses.
  • Consequence: the wakeup path uses its own waker (native reader-slot waker; detached-thread sleep only as an offline fallback) instead of tokio::sync::Notify (see §3); the tokio-glue feature switches to tokio wakeup.

D-2: Tokio glue as an optional feature

  • Choice: --features tokio-glue enables tokio-specific convenience.
  • Rationale: tokio dominates (~85 % market share); the glue offers comfort without coercion.
  • Consequence: the default build has no tokio dep; workspace CI stays lean.

D-3: API symmetry with the sync API

  • Choice: method names identical (write, take, dispose, …) — no _async suffix.
  • Rationale: the newtype pattern makes the sync↔︎async switch a pure type change; caller code does not change.
  • Consequence: the caller MUST import AsyncDataWriter instead of DataWriter — no name-collision risk because of a different module/path.

D-4: WaitSet stays sync

  • Choice: WaitSet is NOT converted to async in 1.0.
  • Rationale: WaitSet is a spec DCPS construct with clear block semantics; Stream<Item = ConditionEvent> is a separate API layer and needs its own design.
  • Consequence: a caller that needs WaitSet uses the sync API. The async pattern via take_stream + listener streams.

D-5: Listener bridge as a stream wrapper

  • Choice: sync listeners stay; the async bridge offers a stream variant.
  • Rationale: spec §2.2.2.4.4 listeners are callbacks — an async caller prefers while let Some(ev) = stream.next().await.
  • Consequence: listener streams are separate methods; the sync listener set stays the default.

zerodds-async 1.0 — Spec-Coverage

Quelle: docs/specs/zerodds-async-1.0.md (Vendor-Spec, draft 2026-05-04).

Implementation:

§1 Type-Mapping zur Sync-API

§1.1 AsyncDomainParticipantFactory

  • Anforderung: Singleton, share Sync-Factory; create_participant async-fähig.
  • Repo: crates/dcps-async/src/factory.rs
  • Tests: crates/dcps-async/tests/smoke.rs::factory_singleton_offline_participant
  • Status: done

§1.2 AsyncDomainParticipant

  • Anforderung: newtype um DomainParticipant; create_topic, create_publisher, create_subscriber identisch zu sync.
  • Repo: crates/dcps-async/src/participant.rs
  • Tests: tests/smoke.rs (alle nutzen create_participant_offline + create_topic + create_publisher/subscriber)
  • Status: done

§1.3 AsyncPublisher / AsyncSubscriber

  • Anforderung: newtype, create_datawriter/datareader liefert AsyncDataWriter/Reader.
  • Repo: crates/dcps-async/src/{publisher,subscriber}.rs
  • Tests: tests/smoke.rs::writer_write_async_offline, reader_take_returns_empty_after_timeout
  • Status: done

§1.4 AsyncDataWriter

  • Anforderung: newtype + Send/Sync; teilt internen Arc<DataWriter<T>>.
  • Repo: crates/dcps-async/src/writer.rs
  • Tests: tests/smoke.rs::writer_write_async_offline, writer_register_dispose_unregister_async
  • Status: done

§1.5 AsyncDataReader

  • Anforderung: dito.
  • Repo: crates/dcps-async/src/reader.rs
  • Tests: tests/smoke.rs::reader_take_returns_empty_after_timeout
  • Status: done

§2 Methodensignaturen

§2.1.1 AsyncDataWriter::write

  • Anforderung: async fn write(&self, &T) -> Result<()>; suspendiert bei OutOfResources statt zu blockieren.
  • Repo: crates/dcps-async/src/writer.rs::write (yield_for-Retry-Loop bis reliability.max_blocking_time abgelaufen ist; Spec §5.1).
  • Tests: tests/smoke.rs::{writer_write_async_offline, write_returns_timeout_after_max_blocking_when_queue_full}
  • Status: done — bei OutOfResources yield + retry; Timeout-Result-Pfad mit endlicher max_blocking_time getestet.

§2.1.2 AsyncDataWriter::register_instance

  • Anforderung: async fn analog sync.
  • Repo: crates/dcps-async/src/writer.rs::register_instance
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.3 AsyncDataWriter::dispose

  • Anforderung: async fn; löst Wire-Lifecycle DISPOSED.
  • Repo: crates/dcps-async/src/writer.rs::dispose
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.4 AsyncDataWriter::unregister_instance

  • Anforderung: async fn; UNREGISTERED + autodispose-Flag.
  • Repo: crates/dcps-async/src/writer.rs::unregister_instance
  • Tests: tests/smoke.rs::writer_register_dispose_unregister_async
  • Status: done

§2.1.5 AsyncDataWriter::wait_for_matched_subscription

  • Anforderung: async fn; resolves Ok bei min_count, Err(Timeout) bei timeout.
  • Repo: crates/dcps-async/src/writer.rs::wait_for_matched_subscription
  • Tests: tests/smoke.rs::wait_for_matched_subscription_times_out_when_no_reader
  • Status: done

§2.1.6 AsyncDataWriter::matched_subscription_count

  • Anforderung: synchron — non-async-Eigenschaft.
  • Repo: crates/dcps-async/src/writer.rs::matched_subscription_count
  • Tests: indirekt über wait_for_matched_subscription
  • Status: done

§2.2.1 AsyncDataReader::take_stream

  • Anforderung: fn take_stream() -> impl Stream<Item = Sample<T>> + Send.
  • Repo: crates/dcps-async/src/reader.rs::take_stream + SampleStream
  • Tests: kompiliert + builder-Test (smoke)
  • Status: done — Stream nutzt den nativen Reader-Slot-Waker (register_user_reader_waker, Wake bei Sample-Arrival, kein Polling); im Offline-Mode dient detached-thread-Sleep als Fallback (Spec §3.3).

§2.2.2 AsyncDataReader::take

  • Anforderung: async fn take(timeout) -> Result<Vec<Sample<T>>>.
  • Repo: crates/dcps-async/src/reader.rs::take
  • Tests: tests/smoke.rs::reader_take_returns_empty_after_timeout
  • Status: done

§2.2.3 AsyncDataReader::wait_for_matched_publication

  • Anforderung: dito wait_for_matched_subscription.
  • Repo: crates/dcps-async/src/reader.rs::wait_for_matched_publication
  • Tests: indirekt über publication_matched_stream-Test
  • Status: done

§2.2.4 AsyncDataReader::matched_publication_count

  • Anforderung: synchron.
  • Repo: crates/dcps-async/src/reader.rs::matched_publication_count
  • Tests: indirekt
  • Status: done

§3 Waker-Modell

§3.1 Waker-Slot pro Reader

  • Anforderung: UserReaderSlot bekommt async_waker: Mutex<Option<Waker>>.
  • Repo: crates/dcps/src/runtime.rs::UserReaderSlot::async_waker
  • Tests: indirekt via SampleStream-Live-Pfad
  • Status: done

§3.2 Wire-Pfad weckt Waker

  • Anforderung: deliver_to_reader_slot weckt Waker, sobald sample_tx.send.
  • Repo: crates/dcps/src/runtime.rs::wake_async_waker (nach jedem sample_tx.send im handle_user_datagram-Pfad)
  • Tests: indirekt
  • Status: done

§3.3 Stream::poll_next registriert Waker

  • Anforderung: Pending-Branch speichert cx.waker().clone() im Slot.
  • Repo: crates/dcps-async/src/reader.rs::SampleStream::poll_next (Live-Mode via runtime_handle + register_user_reader_waker)
  • Tests: kompiliert
  • Status: done — Live-Mode nativ; Offline-Mode behält detached-thread-Fallback.

§4 Tokio-Glue (Feature)

§4.1 spawn_in_tokio

  • Anforderung: Mit --features tokio-glue: AsyncDomainParticipantFactory::spawn_in_tokio.
  • Repo: crates/dcps-async/src/factory.rs::AsyncDomainParticipantFactory::spawn_in_tokio (+ _with_qos); treibt den DDS-Tick-Loop als tokio-Task statt dediziertem std::thread (via RuntimeConfig::external_tick + DcpsRuntime::tick_driver()).
  • Tests: crates/dcps/tests/external_tick.rs + crates/dcps-async/tests/spawn_in_tokio.rs.
  • Status: done — spawn_in_tokio treibt den Tick im tokio-Executor (spart einen Thread pro Participant).

§5 Backpressure & Resource-Limits

§5.1 write-Future suspendiert bei OutOfResources

  • Anforderung: Bei DdsError::OutOfResources awaitet drain_notify; bei OK kehrt zurück.
  • Repo: crates/dcps-async/src/writer.rs::AsyncDataWriter::write — yield_for-Retry-Loop bis reliability.max_blocking_time abläuft (dann DdsError::Timeout); Caller-Future bleibt schlafend statt zu spinnen.
  • Tests: tests/smoke.rs::write_returns_timeout_after_max_blocking_when_queue_full
  • Status: done — die write-Future bleibt schlafend (yield_for-Retry bis max_blocking_time, dann Timeout) statt zu spinnen; ein nativer drain-Notify-Hook statt Retry wäre eine mögliche Optimierung, sobald der Sync-Writer einen Drain-Channel exposed.

§6 Listener-Bridge

§6.1 data_available_stream

  • Anforderung: fn data_available_stream() -> impl Stream<Item = ()> + Send.
  • Repo: crates/dcps-async/src/reader.rs::DataAvailableStream (Polling-Probe: reader.is_ready()-Loop, kein konsumieren).
  • Tests: kompiliert (lib + tests).
  • Status: done — Listener-Stream baut auf nicht-konsumierender Probe; nativer Wakeup hängt an §3 (Reader-Slot-Waker), der live ist.

§6.2 publication_matched_stream

  • Anforderung: fn publication_matched_stream() -> impl Stream<Item = PublicationMatchedStatus> + Send.
  • Repo: crates/dcps-async/src/reader.rs::PublicationMatchedStream
  • Tests: tests/smoke.rs::publication_matched_stream_yields_initial_count
  • Status: done — yieldet usize (Match-Count) bei jeder Aenderung.

§7 Error-Mapping

§7.1 DdsError unverändert

  • Anforderung: Future::Output ist Result<T, DdsError> ohne Async-spezifische Error-Varianten.
  • Repo: alle Methoden in crates/dcps-async/src/{writer,reader}.rs returnen Result<T, DdsError>.
  • Tests: tests/smoke.rs::wait_for_matched_subscription_times_out_when_no_reader matcht DdsError::Timeout.
  • Status: done

§8 Test-Strategie

§8.1 Async-Pendants pro Sync-Test

  • Anforderung: Pro Sync-Test in crates/dcps/tests/ einen async-Pendant.
  • Repo: crates/dcps-async/tests/{smoke,proptest_backpressure,cyclone_live_async_e2e}.rs
  • Tests: smoke deckt happy-path; proptest deckt Random-Sequenzen.
  • Status: done — smoke + proptest + Cyclone-Live-E2E vorhanden.

§8.2 Tokio-Test-Runner

  • Anforderung: tokio::test als default; smol-Variante als feature-Probe.
  • Repo: crates/dcps-async/tests/smoke.rs (alle Tests #[tokio::test(flavor = "multi_thread")]).
  • Tests: Cargo.toml dev-dep tokio = { features = ["rt-multi-thread", "macros", ...] }.
  • Status: done — tokio-Default; smol-Variante bleibt offen (kein Mehrwert solange tokio-glue der einzige Runtime-Hook ist).

§8.3 proptest über Channel-Backpressure

  • Anforderung: proptest über zufällige write/take-Sequenzen — Backpressure-Invariante.
  • Repo: crates/dcps-async/tests/proptest_backpressure.rs::write_take_sequence_holds_invariants.
  • Tests: 16 Cases mit zufälliger Capacity ∈ [1,8] + Op-Vec ∈ [0,32]; verifiziert: bei voller Queue MUSS write Timeout liefern, bei freier Queue MUSS Ok kommen.
  • Status: done.

§8.4 E2E gegen Cyclone-Live

  • Anforderung: E2E gegen Cyclone-Live wie Sync; Latenz-Vergleich Sync vs Async.
  • Repo: crates/dcps-async/tests/cyclone_live_async_e2e.rs::async_reader_does_not_panic_against_live_cyclone_pub (#[ignore]-gated, SSH-Lab-Setup); Latenz-Vergleich via crates/dcps-async/benches/write_async_vs_sync.rs + CI bench-main.
  • Tests: Live-E2E mit BENCH_HOST_AVAILABLE=1 + cargo test -- --ignored opt-in.
  • Status: done — Live-Test als #[ignore]-Opt-in eingerichtet, Quantitative Latenz-Antwort liefert §9.1.

§9 Performance-Targets

§9.1 write().await Latenz

  • Anforderung: ≤ 5 % Overhead gegen sync-write (criterion bench).
  • Repo: crates/dcps-async/benches/write_async_vs_sync.rs + .gitlab-ci.yml::bench-main (cargo bench -p zerodds-dcps-async --bench write_async_vs_sync -- --save-baseline pre) + bench-compare Regression-Check.
  • Tests: Bench läuft auf jedem main-Push; Regression > 10% rot via tests/perf/check_bench_regressions.py.
  • Status: done — Bench in CI-Pipeline aktiv.

§9.2 take_stream Throughput

  • Anforderung: Kein Sample-Verlust durch Polling-Latenz; 100 % Sample-Rate.
  • Repo:
  • Tests:
  • Status: open — Bench für take_stream-Throughput offen.

§9.3 Allokation pro write()

  • Anforderung: 0 Heap-Allokationen extra gegen sync.
  • Repo:
  • Tests:
  • Status: open — dhat-rs-Bench offen.

§10 Decisions

D-1: Runtime-agnostische API als Default

  • Wahl: Public-API liefert impl Future/impl Stream ohne tokio-Pin.
  • Begründung: zerodds soll nicht eine Runtime erzwingen; Caller wählt.
  • Konsequenz: Wakeup-Pfad nutzt einen eigenen Waker (nativen Reader-Slot-Waker; detached-thread-Sleep nur als Offline-Fallback) statt tokio::sync::Notify (siehe §3); das tokio-glue-Feature schaltet auf tokio-Wakeup um.

D-2: Tokio-Glue als optional Feature

  • Wahl: --features tokio-glue aktiviert tokio-spezifische Convenience.
  • Begründung: Tokio dominiert (~85 % Marktanteil); Glue bietet Komfort ohne Zwang.
  • Konsequenz: Default-Build hat keine tokio-Dep; Workspace-CI bleibt schlank.

D-3: API-Symmetrie zur Sync-API

  • Wahl: Methodennamen identisch (write, take, dispose, …) — kein _async-Suffix.
  • Begründung: Newtype-Pattern macht den Switch sync↔︎async zu einem reinen Type-Wechsel; Caller-Code ändert sich nicht.
  • Konsequenz: Caller MUSS importieren AsyncDataWriter statt DataWriter — kein name-collision-Risk weil different module/path.

D-4: WaitSet bleibt sync

  • Wahl: WaitSet wird NICHT async-konvertiert in 1.0.
  • Begründung: WaitSet ist Spec-DCPS-Konstrukt mit klaren Block-Semantik; Stream<Item = ConditionEvent> ist eine separate API-Schicht und braucht eigenes Design.
  • Konsequenz: Caller, der WaitSet braucht, nutzt sync-API. Async-Pattern via take_stream + listener-streams.

D-5: Listener-Bridge als Stream-Wrapper

  • Wahl: sync-Listeners bleiben; async-Bridge bietet Stream-Variante.
  • Begründung: Spec §2.2.2.4.4-Listeners sind Callbacks — async-Caller will lieber while let Some(ev) = stream.next().await.
  • Konsequenz: Listener-Streams sind separate Methoden; sync-Listener-Set bleibt Default.