You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(streaming): expose a configurable wait strategy across every binding (#806)
* feat(streaming): expose a configurable wait strategy across every binding
The streaming event-ring consumer applies a wait strategy on every ring-empty poll; this was previously fixed. A `StreamingWaitStrategy` preset now selects the latency-vs-CPU trade-off across every binding: `LowLatency` (the default, which reproduces the previous fixed behaviour byte-for-byte — spin, yield, `spin_loop` hint, never sleeps), `Balanced` (a short spin then a brief timed park), `Efficient` (a longer park for the lowest idle CPU), and `BusySpin` (pure spin, pinning a core). The spin, yield, and park counts tune independently and are clamped to sane bounds.
The preset enum plus the numeric `wait_spin_iters` / `wait_yield_iters` / `wait_park_us` knobs are the FFI-safe form of the override, exposed identically on Python, TypeScript, C++, and the C ABI (with `THETADATADX_WAIT_*` selectors), and enforced cross-binding by the parity guard. Rust additionally gains a zero-cost generic `StreamingClient::for_each_with_wait_strategy` that accepts any user `WaitStrategy` impl; that override is Rust-only by design, because `wait_for` fires on every ring-empty poll and routing a per-poll callback across the C ABI, the CPython lock, or the JavaScript event loop would be a latency regression rather than a tuning knob.
The same surface adds an optional `consumer_cpu` knob that pins the tick-consumer drain thread to a CPU core for deterministic, low-jitter delivery; `None` (a negative sentinel on the C ABI) is the default and leaves the thread under the OS scheduler, so behaviour is unchanged unless a client opts in. An out-of-range or offline core is a best-effort no-op at the affinity layer rather than a hard error. The config-derived strategy threads through the builder into both the ring build site and the consumer-side blocking drain; the disruptor builder erases the wait-strategy type after build, so the poller and producer return types are unchanged and the ring API stays stable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(ffi): name the per-call SAFETY invariant in the wait-strategy test
The wait-strategy C ABI round-trip test repeated a stamped "see the module-level note" SAFETY comment across several unsafe blocks, which the SAFETY-comment hygiene gate rejects. Rewrite each to name the actual invariant — the live, unfreed cfg handle and the valid stack out-params for that specific call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(streaming): pin the consumer core on the wait-strategy override path and de-flake the preset test
The Rust-only for_each_with_wait_strategy override drained the ring without applying the configured consumer-core affinity, so a caller that set consumer_cpu and used the override lost the pin; call pin_consumer_once at its drain entry like the other drain paths so the config knob applies consistently regardless of which drive API is used.
Replace the fragile absolute upper-bound timing assertion on the never-sleeping low-latency wait_for with a relative comparison against the Balanced preset's real park, so the test does not flake when a loaded host stretches the yield phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: remove a third-party project reference from the historical notes
Drop a parenthetical naming an external project's repository layout from the historical changelog, its rendered mirror, and the matching release note. The repository's structure stands on its own description; referencing another project's layout adds nothing and does not belong in public release prose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: correct the wait-strategy override doc link and sync tool lockfiles
Point the streaming wait-strategy docstring at the real `for_each_with_wait_strategy` method (the rustdoc intra-doc-link gate rejected the stale `start_streaming_with_wait_strategy` name), and resync the tools/mcp and tools/server lockfiles so the excluded-workspace `--locked` builds match their manifests after the new affinity dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
39
39
40
40
### Added
41
41
42
+
- Configurable streaming latency tuning across every binding: a `StreamingWaitStrategy` event-ring wait-strategy preset (`LowLatency` default, which preserves the previous fixed behaviour, plus `Balanced`, `Efficient`, and `BusySpin`) with independent spin / yield / park tuning, and an optional `consumer_cpu` knob that pins the tick-consumer thread to a CPU core (`None` default leaves it under the OS scheduler); Rust additionally exposes a zero-cost generic `StreamingClient::for_each_with_wait_strategy` override that accepts any user `WaitStrategy` impl, with the preset enum plus numeric tuning as the FFI-safe form on Python (`wait_strategy` / `wait_spin_iters` / `wait_yield_iters` / `wait_park_us` / `consumer_cpu`), TypeScript (`waitStrategy` / `setWaitStrategy` and the matching numeric and `consumerCpu` setters/getters), C++ (`set_wait_strategy` / `get_wait_strategy` and the numeric / `consumer_cpu` forwarders), and the C ABI (`thetadatadx_config_set_wait_strategy` with `THETADATADX_WAIT_*` selectors, the numeric setters/getters, and `thetadatadx_config_set_consumer_cpu` with a negative `THETADATADX_CONSUMER_CPU_UNPINNED` sentinel).
42
43
- Epoch-instant accessors on every row that carries `date` plus a milliseconds-of-day column, computed on read (raw integer fields stay primary): Rust methods and Python properties named `timestamp_ms` for the bare `ms_of_day` column and `<prefix>_timestamp_ms` for prefixed columns (`created_timestamp_ms`, `last_trade_timestamp_ms`, `underlying_timestamp_ms`, `quote_timestamp_ms`), the C function `thetadatadx_timestamp_ms(date, ms_of_day)`, and the C++ `thetadatadx::timestamp_ms` wrapper. All return Unix epoch milliseconds (UTC, DST-aware) and signal absent dates (`None` / `-1`).
43
44
-`thetadatadx::time::date_ms_to_epoch_ms` — the DST-aware inverse of the epoch-to-Eastern split, shared by every accessor above.
44
45
-`thetadatadx::CalendarStatus` — the exported calendar day-type enum with `as_str()` / `from_wire_text()` / `from_code()` / `is_open()`.
@@ -617,8 +618,8 @@ code does not change.
617
618
surfaces as a pre-merge agreement failure rather than going
618
619
unnoticed until a downstream consumer hit the missing / extra
619
620
field.
620
-
- Repo hygiene pass. Root tree trimmed to standard institutional shape
621
-
(matches the databento-rs layout): moved `ROADMAP.md` → `docs/`,
621
+
- Repo hygiene pass. Root tree trimmed to a standard institutional
622
+
layout: moved `ROADMAP.md` → `docs/`,
622
623
moved `config.default.toml` into the `thetadatadx` crate, deleted unused
623
624
`cliff.toml`. Architecture ADRs inlined into source-code comments at
624
625
their relevant locations; `docs/architecture/` removed. Generated
0 commit comments