Skip to content

Releases: lazily-hub/lazily-rs

lazily v0.12.0

17 Jun 23:03

Choose a tag to compare

lazily v0.12.0

Minor release over v0.11.1. Last published: crates.io 0.11.1.
Tag v0.12.0 points at this release commit on main.

Highlights

Correctness fixes to the networked str0m DataChannel backend (Str0mNet,
behind the opt-in webrtc-str0m feature). Two bugs that silently lost data are
fixed; one of them adds a new public error variant (the single breaking surface
in this release). The core reactive primitives (Context / ThreadSafeContext
/ AsyncContext, slots, cells, effects, signals) are unchanged.

Breaking (scoped to the webrtc-str0m feature)

  • New Str0mNetError::Backpressure variant (#lzstr0mframe). The
    Str0mNetError enum is not #[non_exhaustive], so adding a variant is a
    breaking change for downstream code with an exhaustive match on it. The
    variant is returned by Str0mNetChannel::send_frame once the driver's
    outbound queue reaches MAX_PENDING_FRAMES (1024), so callers apply flow
    control instead of growing memory without bound. Callers matching
    Str0mNetError exhaustively must add a Backpressure arm (yield and retry
    send_frame). Only the webrtc-str0m feature surface is affected; no other
    public API changed.

Fixes

  • #lzstr0mframe — silent frame loss under SCTP backpressure. The driver's
    flush loop used if ch.write(...).is_err() { break; }, which discarded the
    bool returned by Channel::write. str0m returns Ok(false) when the SCTP
    send buffer is full (backpressure); the old code treated that identically to
    Err, silently dropping every frame that exceeded the SCTP send window
    violating the ordered/reliable DataChannel invariant WebRtcSink /
    WebRtcSource rely on. Post-fix the Ok(false) and Err paths both
    re-queue the frame and yield, letting the next poll_output / recv_from
    cycle drain the window (for Ok(false)) or detect a dead Rtc on the next
    is_alive() check (for Err). Combined with the new bounded queue +
    Backpressure variant, sustained bursts can no longer exhaust memory or lose
    frames. Regression test burst_of_frames_arrives_in_order_under_backpressure
    bursts 100 × 8 KiB frames through one channel and asserts all 100 arrive in
    order, retrying on Backpressure as needed.
  • #lzstr0mpolldrive — surface driver I/O errors. The driver's UDP transmit
    path was let _ = socket.send_to(...), discarding every error: ENOBUFS
    (send-buffer pressure), ECONNREFUSED (ICMP port-unreachable, peer down),
    ENETUNREACH / EHOSTUNREACH (route flap), EBADF (socket closed). The
    corresponding ICE/DTLS/SCTP packet was silently lost and the handshake / data
    path stalled without diagnostics. Post-fix, WouldBlock / Interrupted
    (retryable on a blocking socket) continue the drain loop — str0m re-emits the
    Transmit on a later poll_output; any other error breaks the driver
    ('outer), surfacing Closed so the caller re-signals. The read-timeout cap
    is also documented as a command-poll interval (COMMAND_POLL_INTERVAL, 15 ms),
    not a str0m timing parameter.

Verification

  • cargo build --all-features, cargo clippy --all-features --all-targets -- -D warnings,
    cargo fmt --check clean.
  • cargo test --features "signaling-client webrtc-str0m" --test str0m_net --test webrtc_signaling --test webrtc_transport
    — 7/7 pass, including burst_of_frames_arrives_in_order_under_backpressure.
  • Full default + feature test suites pass (107 spec/integration tests + loom +
    stress + tokio + async + ffi + ipc + websocket).

Pre-existing, unrelated: benchmark-check

make check ends in benchmark-check, which is red at this commit AND was red
at tag v0.11.1
(already shipped): stale thread_safe instrumentation
lock-acquisition budgets that pre-date this release's content. The four overshoots
are in thread_safe contention benchmarks (unrelated module); src/str0m_net.rs
is the only source touched by v0.12.0 and cannot affect those counts. Tracked
separately in tasks/software/lazily-rs.md as #lzbenchbudget.

Publish checklist

  1. cargo publish (dry-run verified clean).
  2. gh release create v0.12.0 --notes-file RELEASE_NOTES_v0.12.0.md --title "lazily v0.12.0".
  3. Rotate the crates.io token if expired before step 1.

lazily v0.11.1

17 Jun 18:57

Choose a tag to compare

lazily v0.11.1

Patch release over v0.11.0. Last published: crates.io 0.11.0.
Tag v0.11.1 points at this release commit on main.

Highlights

Hardening and verification-coverage fixes over the v0.11.0 reactive core. No
API changes, no new features, no breaking behavior — safe bump for downstream
users. All changes are patch-level and backward-compatible.

Fixes

  • #lzrepourlCargo.toml repository field corrected from the stale
    btakita/lazily-rs to the canonical lazily-hub/lazily-rs (matching
    book.toml and git remote origin). Fixes the crates.io/docs.rs repository
    link.
  • 1cee080EncodeError/DecodeError enums in src/ipc.rs are now
    gated on #[cfg(any(feature = "ffi", feature = "ipc-binary"))] to match their
    impl blocks. Eliminates dead-code warnings when building with webrtc-str0m
    (which enables ipc without ffi/ipc-binary).
  • 8665e29 — hardened all 12 id.0 as usize casts in thread_safe.rs to
    usize::try_from via a node_index() helper, preventing silent truncation on
    32-bit targets. Added From<serde_json::Error> (ffi) and
    From<postcard::Error> (ipc-binary) for EncodeError/DecodeError for
    ergonomic ? propagation.
  • #lzloomtimeout — the inline_seqlock_envelope_rejects_torn_and_stale_under_concurrent_publish
    Loom exhaustive model never terminated (>5 min state-space blowup across the
    full 6-atomic envelope + two threads + driving body). Switched to a
    preemption-bounded loom::model::Builder (bound 4, 60 s duration cap). The
    bound is validated by injecting a torn-read regression and confirming the
    model still flags it. The two single-property seqlock models stay exhaustive;
    SPEC and source doc-comments corrected from "exhaustive" to
    "preemption-bounded" for the combined envelope model.

Verification

  • cargo build --all-features, cargo clippy --all-features --all-targets -- -D warnings,
    cargo fmt --check clean.
  • Full default + feature test suites pass.
  • cargo test --features loom --test thread_safe_loom: 15/15 in ~82 s (was
    non-terminating).
  • cargo publish --dry-run: 72 files, ~234 KiB compressed, clean.

Publish checklist

  1. cargo publish (dry-run verified clean).
  2. gh release create v0.11.1 --notes-file RELEASE_NOTES_v0.11.1.md --title "lazily v0.11.1".
  3. Rotate the crates.io token if expired before step 1.

lazily v0.11.0

15 Jun 21:44

Choose a tag to compare

lazily v0.11.0

Prepared for the operator publish (#12b1). Last published: crates.io 0.10.3.
Tag v0.11.0 is intended to point at this release commit on main.

Highlights

This minor lands eager Signal primitives across the single-threaded,
thread-safe, and async contexts, plus a full WebRTC DataChannel transport stack
on top of the v0.10.x reactive core: a sans-IO str0m backend, a real-socket
networked backend, a WebSocket fallback, signaling, and the glue that drives a
complete handshake end to end.

Features

  • #3dmm / #x7sp - add SignalHandle, ThreadSafeSignalHandle, and
    AsyncSignalHandle as eager derived values backed by memo slots plus puller
    effects. Signals provide always-materialized v1 -> v2 updates with no
    observable unset window, inherit memo equality suppression, and are documented
    in the README, SPEC, PROTOCOL, and mdBook docs.
  • #lzwebrtcwire — wire SignalingClient to Str0mNet. New
    webrtc_signaling module (offer_to_peer / answer_next_offer) owns the full
    SDP offer/answer + trickled-ICE handshake over SignalingClient, pumping
    frames into accept_answer / add_remote_candidate until the data channel
    opens. Integration test brokers two real SignalingClient WebSocket peers
    through an in-process #yxjw-protocol loopback relay and proves a
    permission-filtered Snapshot crosses the negotiated channel.
  • #lzwebrtcnet — networked str0m DataChannel backend (Str0mNet) over a
    real UDP socket with the str0m DTLS/SCTP/ICE driver.
  • #97xn — multi-channel reactive bridge hub.
  • #akp3 — WebSocket DataChannel backend (in-process loopback over a real
    WS handshake).
  • #webrtcbackend — concrete sans-IO str0m DataChannel backend.
  • #webrtc2 / #webrtc3 — WebRTC DataChannel IPC transport abstraction,
    loopback integration tests, and Criterion benchmarks.

CI / tests

  • #lzleanmodel - CI now builds the sibling Lean formal model so protocol
    invariants stay checked alongside the Rust suite.
  • #lzspecconf — IPC conformance run against the canonical lazily-spec
    fixtures.
  • #k03k / #lzasync — deterministic async resolve-loop window coverage.

Remaining (operator-gated)

  • Live two-host / NAT validation of Str0mNet through the deployed #yxjw
    Cloudflare Worker (#lzwebrtcnet-e2e, part of #h6qb) — cannot be done in CI.

Publish checklist (#12b1)

  1. cargo publish (dry-run verified clean: 72 files, 233 KiB compressed).
  2. gh release create v0.11.0 --notes-file RELEASE_NOTES_v0.11.0.md --title "lazily v0.11.0".
  3. Rotate the crates.io token if expired before step 1.

v0.10.1 — fix async get_async benign-race panics

05 Jun 07:31

Choose a tag to compare

lazily 0.10.1

Patch release. Fixes a concurrency bug in the async feature (#k03k).

get_async no longer panics on benign races

ThreadSafeContext was unaffected; this is AsyncContext-only. Under contended set_cell + get_async, get_async asserted two states that are reachable under concurrency and panicked:

  • unreachable!("get() already checked Resolved") — the slot can transition Computing → Resolved between the get() fast-path check (which drops the lock) and the re-lock.
  • panic!("get_async: notifier dropped unexpectedly") — a superseded (stale-revision) or invalidated compute drops all watch senders without a final Resolved send.

get_async is now an outer re-resolve loop that reads authoritative slot state and, on a dropped notifier, restarts to observe the current state instead of aborting. The published value was always correct — this only removes the panics.

Regression test (async_context_concurrent_set_and_get_async_never_panics_k03k, multi-thread tokio, 8 workers × 250 iters) fails deterministically against 0.10.0 and passes on 0.10.1. No thread_safe/API changes since 0.10.0.

v0.10.0 — selectable read strategy + inline small-Copy seqlock

05 Jun 06:41

Choose a tag to compare

lazily 0.10.0

Two ThreadSafeContext cached-read improvements.

Runtime-selectable read strategy (#rdstrat1)

Both the low-concurrency (parking_lot::RwLock) and high-concurrency (arc-swap) cached-read paths are compiled in and chosen at context construction via ThreadSafeContext::with_read_strategy.

Breaking default change: the default flips from arc-swap (0.9.0) to LowConcurrency — opt into HighConcurrency explicitly. arc-swap wins at 8+ cores but regresses uncontended; LowConcurrency is the better default for typical workloads.

Inline small-Copy seqlock fast path (#rdstrat2)

New CachedReadStorage::Inline variant stores small Copy slot values inline behind a wait-free single-writer / multi-reader seqlock ([AtomicU8; 24], relaxed per-byte atomics, crossbeam-style seq fences) — no heap Arc, no refcount traffic on read or publish, optimal under both read strategies.

  • Opt-in via new slot_copy / computed_copy / memo_copy constructors. Automatic Copy detection is impossible on stable Rust (pre-monomorphization method resolution can't select a Copy-gated impl without the bound; needs nightly specialization).
  • Transparent fallback to the strategy path above 24 bytes or alignment 16.
  • Exhaustively Loom-modeled: torn-read refusal, full-envelope torn+stale refusal under concurrent publish, and post-invalidation stale rejection.

Full make check (fmt, clippy -D warnings, all feature suites, Loom 15/15) green.

v0.9.0

05 Jun 00:37

Choose a tag to compare

Full Changelog: v0.8.0...v0.9.0

v0.6.0

04 Jun 01:28

Choose a tag to compare

v0.6.0

Performance

  • #lazyperf33: Hybrid InvalidationPlan — Vec for ≤16 nodes, HashMap above threshold
  • #lazyperf34: Fix finish_batch O(n²) dedup — extend + sort_unstable + dedup
  • #lazyperf35: Eliminate scratch buffer allocations on effect path
  • #lazyperf36: Add std_sync_mutex feature for A/B benchmarking
  • #lazyperf25–32: Vec node storage, SmallVec edges, Rc closures, parking_lot mutex, Vec-based invalidation plans, slot ID reuse

Features

  • Handle-centric get methods for CellHandle and SlotHandle
  • vec_edges and std_sync_mutex feature flags for benchmarking
  • Thread-safe invalidation plan with latency evidence
  • Contention regression budgets and effect contention profiles

Infrastructure

  • GitHub Actions CI workflow
  • BENCHMARKS.md extracted from README
  • make check workflow for local verification

crates.io

lazily 0.5.0

02 Jun 04:26

Choose a tag to compare

Full Changelog: v0.4.0...v0.5.0

v0.1.1

15 Mar 07:03

Choose a tag to compare

Includes README, AGENTS.md, and comprehensive spec compliance test suite (36 tests).

No API changes from v0.1.0.

v0.1.0

15 Mar 06:39

Choose a tag to compare

Initial release of lazily — lazy reactive signals for Rust.

Features

  • Context — container for all slots and cells
  • SlotHandle<T> — lazily-computed cached values with automatic dependency tracking
  • CellHandle<T> — mutable values with PartialEq-guarded invalidation
  • Thread-local tracking stack for automatic dependency discovery
  • Lazy semantics: dependents cleared on invalidation, recomputed only on access
  • Dynamic dependency graphs (edges re-discovered on recomputation)

Crate

https://crates.io/crates/lazily