Skip to content

Commit c225c09

Browse files
userFRMclaude
andauthored
chore(fpss): remove the dead wait-strategy escape hatch + hand-rolled splitmix (#1200)
for_each_with_wait_strategy<W> and its streaming::wait re-export module had no caller but their own test — a public generic that can't cross the FFI and that the bindings never use (they drain through for_each_scoped). Remove the method, the re-export, and the test. seeded_cursor drew its offset from a hand-rolled entropy_u64/splitmix64; use the already-imported rand instead and delete both functions. No behavior change. Co-authored-by: preview <noreply@anthropic.com>
1 parent e995d40 commit c225c09

4 files changed

Lines changed: 2 additions & 150 deletions

File tree

thetadatadx-rs/src/backoff.rs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -133,36 +133,6 @@ pub(crate) fn uniform_duration(lo: Duration, hi: Duration) -> Duration {
133133
Duration::from_nanos(sampled)
134134
}
135135

136-
/// Process-local entropy word for seeding shuffles and cursor offsets
137-
/// where no caller-supplied seed exists. Folds the wall clock with a
138-
/// process-local counter so two clients constructed in the same
139-
/// nanosecond still diverge.
140-
pub(crate) fn entropy_u64() -> u64 {
141-
use std::sync::atomic::{AtomicU64, Ordering};
142-
static COUNTER: AtomicU64 = AtomicU64::new(0);
143-
let tick = COUNTER.fetch_add(1, Ordering::Relaxed);
144-
let now = u64::try_from(
145-
std::time::SystemTime::now()
146-
.duration_since(std::time::UNIX_EPOCH)
147-
.map_or(0, |d| d.as_nanos()),
148-
)
149-
.unwrap_or(u64::MAX);
150-
let pid = u64::from(std::process::id());
151-
splitmix64(now ^ pid.rotate_left(32) ^ tick.wrapping_mul(0x9E37_79B9_7F4A_7C15))
152-
}
153-
154-
/// splitmix64 finaliser — documented mixer, used here only to spread
155-
/// seed bits, never for cryptographic purposes.
156-
pub(crate) fn splitmix64(mut seed: u64) -> u64 {
157-
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
158-
seed ^= seed >> 30;
159-
seed = seed.wrapping_mul(0xBF58_476D_1CE4_E5B9);
160-
seed ^= seed >> 27;
161-
seed = seed.wrapping_mul(0x94D0_49BB_1331_11EB);
162-
seed ^= seed >> 31;
163-
seed
164-
}
165-
166136
#[cfg(test)]
167137
mod tests {
168138
use super::*;
@@ -224,11 +194,4 @@ mod tests {
224194
assert_eq!(JitterMode::parse("bogus"), None);
225195
assert_eq!(JitterMode::default(), JitterMode::Full);
226196
}
227-
228-
#[test]
229-
fn entropy_words_diverge() {
230-
let a = entropy_u64();
231-
let b = entropy_u64();
232-
assert_ne!(a, b, "consecutive entropy words must differ");
233-
}
234197
}

thetadatadx-rs/src/fpss/mod.rs

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,55 +2089,6 @@ impl StreamingClient {
20892089
}
20902090
}
20912091

2092-
/// Drain events through `on_event`, applying a caller-supplied
2093-
/// [`crate::streaming::wait::WaitStrategy`] on each momentarily
2094-
/// empty ring instead of the default low-latency wait.
2095-
///
2096-
/// This is the Rust-native bring-your-own-strategy escape hatch: the
2097-
/// default wait ([`crate::streaming::wait::BusySpinWithSpinLoopHint`]-style
2098-
/// spin) suits real-time market data, but a Rust caller with an
2099-
/// exotic backoff (e.g. an adaptive PID-controlled park, or a
2100-
/// strategy that coordinates with another subsystem) can supply any
2101-
/// `W: WaitStrategy` here. Use a strategy from
2102-
/// [`crate::streaming::wait`] (e.g.
2103-
/// [`crate::streaming::wait::BusySpin`]) or implement the trait on
2104-
/// your own type.
2105-
///
2106-
/// `W` is monomorphised into the loop, so the per-poll cost is the
2107-
/// caller's `wait_for` body with no indirection. Delivery semantics
2108-
/// match [`Self::for_each`]: `on_event` fires exactly once per event
2109-
/// and the loop returns on terminal shutdown after the ring drains.
2110-
///
2111-
/// # Why Rust-only
2112-
///
2113-
/// `wait_for` fires on every ring-empty poll on the hot path. Routing
2114-
/// that per-poll callback across the C ABI, the CPython interpreter
2115-
/// lock, or the JavaScript event loop would add call-boundary
2116-
/// overhead to the single tightest loop in the consumer — a latency
2117-
/// regression, not a tuning knob. The bindings therefore run the
2118-
/// fixed low-latency wait with no override.
2119-
pub fn for_each_with_wait_strategy<W, F>(&self, mut on_event: F, strategy: W) -> PollOutcome
2120-
where
2121-
W: crate::streaming::wait::WaitStrategy,
2122-
F: FnMut(&StreamEvent),
2123-
{
2124-
// The drain owner + CPU pin are recorded inside `poll_batch`, after
2125-
// the staging lock is acquired, so the identity reflects the proven
2126-
// drainer rather than whichever thread merely entered this loop.
2127-
loop {
2128-
match self.poll_batch(&mut on_event) {
2129-
// Return the terminal outcome so the caller can tell a clean
2130-
// shutdown from an I/O-thread fault (`Failed`).
2131-
terminal @ (PollOutcome::Shutdown | PollOutcome::Failed) => return terminal,
2132-
// `Busy` cannot arise from this loop's own poll (it holds no
2133-
// lock when it polls); back off and retry defensively in case
2134-
// an `on_event` callback re-entered a drain.
2135-
PollOutcome::Drained(0) | PollOutcome::Busy => strategy.wait_for(0),
2136-
PollOutcome::Drained(_) => {}
2137-
}
2138-
}
2139-
}
2140-
21412092
/// Polymorphic subscribe — wire-level entry point.
21422093
///
21432094
/// Accepts a typed [`protocol::Subscription`] value built via
@@ -4144,35 +4095,6 @@ mod ring_occupancy_tests {
41444095
);
41454096
}
41464097

4147-
/// The bring-your-own-strategy drain is reachable through the
4148-
/// crate-owned wait-strategy re-export, so a Rust caller never names
4149-
/// the underlying ring crate. Dropping the producer terminates the
4150-
/// drain; `BusySpin`'s `wait_for` is a no-op, so the loop spins only
4151-
/// until the ring reports shutdown.
4152-
#[test]
4153-
fn for_each_with_wait_strategy_accepts_crate_owned_preset() {
4154-
use crate::streaming::wait::BusySpin;
4155-
4156-
let (client, mut producer) = StreamingClient::for_ring_occupancy_test(64);
4157-
for _ in 0..3 {
4158-
assert!(
4159-
publish_one(&mut producer),
4160-
"fresh ring must accept a publish"
4161-
);
4162-
}
4163-
// Drop the producer so the poller observes terminal shutdown once
4164-
// the three published events drain, and the loop returns.
4165-
drop(producer);
4166-
4167-
let mut delivered = 0usize;
4168-
client.for_each_with_wait_strategy(|_event| delivered += 1, BusySpin);
4169-
4170-
assert_eq!(
4171-
delivered, 3,
4172-
"every published event must be delivered before shutdown returns"
4173-
);
4174-
}
4175-
41764098
/// A faulted I/O thread — the `io_loop` unwound and its fault guard set
41774099
/// `io_faulted` before the ring published its shutdown sequence — must
41784100
/// surface through the CALLBACK drain path, not read as a clean shutdown.

thetadatadx-rs/src/grpc/pool.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,8 @@ impl ChannelPool {
209209
/// together after an outage lands its entire first burst on the same
210210
/// upstream connection.
211211
fn seeded_cursor(len: usize) -> AtomicUsize {
212-
let seed = crate::backoff::entropy_u64();
213-
let offset = (seed as usize) % len.max(1);
214-
AtomicUsize::new(offset)
212+
use rand::RngExt;
213+
AtomicUsize::new(rand::rng().random_range(0..len.max(1)))
215214
}
216215

217216
/// Pre-dispatch reservation on a pooled [`Channel`].

thetadatadx-rs/src/lib.rs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -344,38 +344,6 @@ pub mod streaming {
344344
#[cfg(feature = "arrow")]
345345
#[doc(hidden)]
346346
pub use crate::fpss::batch_schema::stream_batch_schema;
347-
348-
/// Consumer wait strategies for the streaming ring.
349-
///
350-
/// The streaming client drains the ring with a fixed low-latency
351-
/// wait, which every language binding uses. A Rust caller that needs
352-
/// an exotic backoff can instead supply any type implementing
353-
/// `WaitStrategy` to
354-
/// [`crate::streaming::StreamingClient::for_each_with_wait_strategy`]. The
355-
/// strategy is monomorphised into the drain loop, so the per-poll
356-
/// cost is the caller's `wait_for` body with no indirection.
357-
///
358-
/// `BusySpin` is the lowest-latency strategy (a true busy spin);
359-
/// `BusySpinWithSpinLoopHint` adds a `spin_loop` hint so the core
360-
/// can save power or switch hyper-threads; `Sleep` parks the thread
361-
/// for a fixed duration between polls.
362-
///
363-
/// ```rust,ignore
364-
/// use thetadatadx::streaming::wait::BusySpin;
365-
///
366-
/// client.for_each_with_wait_strategy(
367-
/// |event| { /* handle event */ },
368-
/// BusySpin,
369-
/// );
370-
/// ```
371-
pub mod wait {
372-
// VOCAB-OK: re-exporting the ring's wait-strategy surface under a
373-
// crate-owned path so callers never name the underlying ring
374-
// crate in their own `use` statements or trait bounds.
375-
pub use disruptor::wait_strategies::{
376-
BusySpin, BusySpinWithSpinLoopHint, Sleep, WaitStrategy,
377-
};
378-
}
379347
}
380348

381349
// ─── Market-data queries ──────────────────────────────────────────────────────

0 commit comments

Comments
 (0)