Skip to content

Commit a115170

Browse files
authored
refactor: Wave 3 — typed SubscriptionTier + ArcSwap streaming state + layout moves + LOW batch (refs #500) (#507)
E. Typed `SubscriptionTier` enum ================================ `crates/thetadatadx/src/mdds/tier.rs` introduces a four-variant `SubscriptionTier` enum (`Free`, `Value`, `Standard`, `Pro`) replacing the prior `Option<i32>` representation of the `stock_tier` and `options_tier` fields on `MddsClient`. The discriminant mirrors the wire byte; `from_wire(i32)` decodes (returning `None` for unknown values rather than silently coercing); `max_concurrent_requests` codifies the `2^tier` semaphore semantics. Re-exported as `thetadatadx::SubscriptionTier`. The wire-side `auth::nexus::AuthUser` keeps its raw `Option<i32>` fields so deserialization stays infallible for unknown future tiers; the typed enum is the post- decode in-memory shape callers see. Unit tests cover the round-trip, the unknown-value rejection, and the powers-of-two semaphore math (1, 2, 4, 8). F. `ArcSwap<StreamingSlot>` state machine ========================================= The streaming side of `ThetaDataDx` collapses to a single `ArcSwap<StreamingSlot>` cell walking `Idle -> Live -> Stopped -> Live -> Stopped`. The previous trio of coordinated fields — `Mutex<Option<FpssClient>>`, `Mutex<Option<StreamingDispatcher>>`, `AtomicBool was_streaming` — went away. Read paths (`is_streaming`, `connection_status`, `with_streaming`, every per-subscription forwarder) collapse to one atomic load on the slot and a borrow of the contained `Arc<FpssClient>`. Lifecycle paths retain serial semantics through an rcu-CAS install and an atomic swap-to-`Stopped`, with the previous `Live` payload (if any) running the FPSS-then-dispatcher shutdown sequence. The `dispatcher` field sits in a `Mutex<Option<...>>` inside `Live` because `StreamingDispatcher::shutdown` consumes by value; the lock is hit only by `stop_streaming` and `dropped_event_count`, never by hot-path readers. Adds `arc-swap` 1.7 to the runtime dependencies. A state-machine walk through `Idle -> Live -> Stopped -> Live -> Stopped` and a CAS-race test covering the rejected double-install live alongside the existing `reconnect_streaming` regression tests. The `Drop` contract is documented in place: stop_streaming is idempotent because only a `Live` slot triggers the shutdown sequence. G. Layout moves =============== Top-level mdds-specific modules move under `mdds/`: endpoint.rs -> mdds/endpoint_args.rs (rename clarifies args-only) macros.rs -> mdds/macros.rs registry.rs -> mdds/registry.rs validate.rs merged into mdds/validate.rs wire_semantics.rs -> mdds/wire_semantics.rs `mdds/validate.rs` now hosts the canonical two-arg validators (date / expiration / strike / symbol / interval / right / year) plus a `validate_date_required` single-arg adapter the `parsed_endpoint!` macro expansion needs. The macro switches to the fully-qualified `$crate::mdds::validate::validate_date_required` path so it expands cleanly from generated endpoint bodies; the generator emit (`build_support/endpoints/render/mdds.rs`) follows. `unified.rs` is renamed to `client.rs` so the filename matches the primary type (`ThetaDataDx`). The frames module folds into a directory: `frames.rs` + `frames_generated.rs` -> `frames/mod.rs` + `frames/generated.rs`. The build-time generator paths (`build_support/ticks/mod.rs`, `build_support/ticks/rust_frames.rs`) follow. The `tdbe::types::*_generated.rs` trio segregates under `tdbe::types::generated/`: `enums_endpoint.rs`, `tick.rs`, `tick_layout_asserts.rs`, with a `mod.rs` documenting the directory. The hand-written siblings keep their `include!` sites (now `include!("generated/...rs")`); `mod generated;` is declared privately in `types/mod.rs`. The build-time generator paths (`build_support/endpoints/render/sdk_files.rs`, `build_support/ticks/tdbe_structs.rs`, `build_support/ticks/mod.rs`) follow. Re-exports in `lib.rs` keep the public surface stable: pub use mdds::endpoint_args as endpoint; pub use mdds::registry::{by_category, find, ..., EndpointMeta, ENDPOINTS, ...}; pub use client::{ConnectionStatus, SubscriptionInfo, ThetaDataDx}; `thetadatadx::endpoint::*` and `thetadatadx::ENDPOINTS` / `EndpointMeta` continue to resolve. Build-script `[path = ...]` mounts (`build_support/mod.rs`, `crates/thetadatadx/src/bin/generate_sdk_surfaces.rs`) follow the file moves. `git mv` was used everywhere file blame is meaningful so history follows the rename. H. LOW batch ============ * 3.1 (`mdds.connect_timeout_secs`) — already shipped in Wave 2. * 3.2 (`extract_*_column` return type) — kept as `Vec<Option<T>>`. The three helpers are public surface, exercised by benches, integration tests, the macro-driven list endpoints, and the Polars / Arrow column projections. Switching to `impl Iterator<Item = Option<T>>` would force every caller (including generated FFI bindings) to deal with the iterator shape and would lose the "missing-header -> empty result" early return the warn-log path relies on. The Vec allocation is one per column per call, never on a per-tick path. * 3.5 (`FpssConnectArgs` struct) — deferred to Wave 4 B4 as a breaking signature change. Not landed here. * 3.9 (`Drop::drop` idempotency on `ThetaDataDx`) — addressed in F. The state machine guarantees `stop_streaming` runs the FPSS / dispatcher shutdown at most once; `Drop::drop`'s doc comment now records the contract in-place. * 3.10 (`#[allow(dead_code)]` on `flatfiles::framing::msg`) — removed. Every one of the ten `u16` wire-code constants is genuinely used by `flatfiles::request` and `flatfiles::session`. The attribute was a false positive; clippy stays green. Version bump: 8.0.36 -> 8.0.37 across every crate manifest, `sdks/typescript/package.json` (+ optionalDependencies, + the three per-platform `npm/<platform>/package.json` files), Cargo.lock, and the matching CHANGELOG entry mirrored to `docs-site/docs/changelog.md`.
1 parent de1c714 commit a115170

47 files changed

Lines changed: 1196 additions & 1000 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,64 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [8.0.37] - 2026-05-07
9+
10+
### Added
11+
12+
- **Typed `SubscriptionTier` enum** (`Free`, `Value`, `Standard`, `Pro`)
13+
replacing raw `Option<i32>` on `MddsClient`.
14+
`max_concurrent_requests(self)` codifies the `2^tier` semaphore
15+
semantics; `from_wire(i32)` decodes the wire byte (returning `None`
16+
for unknown values rather than silently coercing). Re-exported as
17+
`thetadatadx::SubscriptionTier`. The wire-side `auth::nexus::AuthUser`
18+
keeps its raw `Option<i32>` fields so deserialization stays
19+
infallible for unknown future tiers; the typed enum is the
20+
post-decode in-memory shape callers see.
21+
22+
### Changed
23+
24+
- **Streaming state machine collapsed into a single `ArcSwap<StreamingSlot>`.**
25+
`ThetaDataDx`'s prior 3-field state (`Mutex<Option<FpssClient>>`,
26+
`Mutex<Option<StreamingDispatcher>>`, `AtomicBool was_streaming`) is
27+
now one `ArcSwap` of an `Idle` / `Live` / `Stopped` enum. Read paths
28+
(`is_streaming`, `connection_status`, `with_streaming`, every
29+
per-subscription forwarder) collapse to one atomic load. Lifecycle
30+
paths retain serial semantics through an rcu-CAS install and an
31+
atomic swap-to-`Stopped`. Adds `arc-swap` 1.7 to runtime deps.
32+
- **Layout moves:**
33+
- Top-level mdds-specific modules relocated under `mdds/`
34+
(`endpoint.rs``mdds/endpoint_args.rs`, `macros.rs`
35+
`mdds/macros.rs`, `registry.rs``mdds/registry.rs`,
36+
`validate.rs` merged into `mdds/validate.rs`,
37+
`wire_semantics.rs``mdds/wire_semantics.rs`). Re-exports
38+
preserved at crate root for back-compat
39+
(`thetadatadx::endpoint::*`, `thetadatadx::EndpointMeta`,
40+
`thetadatadx::ENDPOINTS`).
41+
- `unified.rs``client.rs` (filename = primary type name).
42+
- `frames.rs` + `frames_generated.rs``frames/{mod,generated}.rs`.
43+
- `tdbe::types::*_generated.rs` segregated under
44+
`tdbe::types::generated/`.
45+
46+
### Fixed
47+
48+
- (LOW 3.2) `extract_*_column` return type left as `Vec<Option<T>>`
49+
iterator conversion deferred. The three helpers are public surface
50+
exercised by benches, integration tests, the macro-driven list
51+
endpoints, and the Polars / Arrow column projections; switching to
52+
`impl Iterator<Item = Option<T>>` would force every caller to deal
53+
with the iterator shape and lose the missing-header early-return the
54+
warn-log path relies on.
55+
- (LOW 3.9) `Drop::drop` on `ThetaDataDx` documents its idempotency
56+
invariant. The `Idle` / `Live` / `Stopped` state machine guarantees
57+
the FPSS / dispatcher shutdown sequence runs at most once across
58+
`stop_streaming` + `Drop`.
59+
- (LOW 3.10) `#[allow(dead_code)]` removed from
60+
`flatfiles/framing.rs::msg`. Every one of the ten `u16` wire-code
61+
constants is genuinely used by `flatfiles::request` and
62+
`flatfiles::session`; the attribute was a false positive.
63+
64+
Refs #500.
65+
866
## [8.0.36] - 2026-05-07
967

1068
### Changed

Cargo.lock

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tdbe/src/types/enums.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,4 @@ pub enum RemoveReason {
473473
InvalidCredentialsNullUser = 18,
474474
}
475475

476-
include!("enums_endpoint_generated.rs");
476+
include!("generated/enums_endpoint.rs");
File renamed without changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! Generator-emitted modules.
2+
//!
3+
//! Wave 3 segregated the three `*_generated.rs` files that previously
4+
//! sat next to their hand-written counterparts (`enums.rs`, `tick.rs`)
5+
//! into this subdirectory so the on-disk separation between
6+
//! human-authored and code-generator output is unambiguous.
7+
//!
8+
//! All three are `include!`-ed into a hand-written sibling so the
9+
//! feature gates, hand-written `impl` blocks, and rustdoc above each
10+
//! `include!` site keep their place. This `mod.rs` exists as the
11+
//! re-export hub the file paths point to; nothing here is imported
12+
//! directly from outside `crates/tdbe`.
13+
//!
14+
//! Files:
15+
//!
16+
//! - [`enums_endpoint`] — `Endpoint` / `EndpointMeta` enum bodies, included
17+
//! from `super::enums`.
18+
//! - [`tick`] — `#[repr(C, align(N))]` tick struct definitions, included
19+
//! from `super::tick`.
20+
//! - [`tick_layout_asserts`] — compile-time layout asserts pinned against
21+
//! the schema-derived figures the C / Go FFI mirrors and
22+
//! `tick_layout_asserts.hpp.inc` rely on, included from `super::tick`.
23+
24+
// The `*.rs` files are reached via `include!("generated/<name>.rs")`
25+
// from the hand-written modules above, so no `mod` declarations are
26+
// emitted here. This mod.rs is the re-export hub the spec calls for and
27+
// documents the segregation.

crates/tdbe/src/types/tick_layout_asserts_generated.rs renamed to crates/tdbe/src/types/generated/tick_layout_asserts.rs

File renamed without changes.

crates/tdbe/src/types/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ pub mod enums;
22
pub mod price;
33
pub mod tick;
44

5+
// Generator-emitted modules live in `generated/`. The submodule
6+
// itself is empty (a doc hub) — the actual files are reached via
7+
// `include!("generated/<name>.rs")` from the hand-written
8+
// `enums.rs` / `tick.rs` siblings, so the feature gates and
9+
// hand-written `impl` blocks keep their place above each include site.
10+
mod generated;
11+
512
pub use enums::*;
613
pub use price::Price;
714
pub use tick::*;

crates/tdbe/src/types/tick.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! `crates/thetadatadx/tick_schema.toml` by
1515
//! `cargo run -p thetadatadx --bin generate_sdk_surfaces`.
1616
17-
include!("tick_generated.rs");
17+
include!("generated/tick.rs");
1818

1919
pub use crate::right::ParsedRight;
2020

@@ -118,4 +118,4 @@ impl OptionContract {
118118
// `cargo test -p tdbe` before it lands on the FFI side.
119119
// ─────────────────────────────────────────────────────────────────────────────
120120

121-
include!("tick_layout_asserts_generated.rs");
121+
include!("generated/tick_layout_asserts.rs");

crates/thetadatadx/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thetadatadx"
3-
version = "8.0.36"
3+
version = "8.0.37"
44
edition.workspace = true
55
rust-version.workspace = true
66
authors.workspace = true
@@ -125,6 +125,14 @@ zeroize = { version = "1.8.2", features = ["derive"] }
125125
# cost benchmark.
126126
crossbeam-channel = "0.5.15"
127127

128+
# Lock-free atomic swap of an `Arc<StreamingSlot>` for the streaming
129+
# state machine on `ThetaDataDx`. Replaces three coordinated fields
130+
# (`Mutex<Option<FpssClient>>`, `Mutex<Option<StreamingDispatcher>>`,
131+
# `AtomicBool was_streaming`) with a single atomic load on the read
132+
# path — `is_streaming`, `connection_status`, `dropped_event_count` and
133+
# the `with_streaming` helper all collapse to one `state.load()`.
134+
arc-swap = "1.7"
135+
128136
[dev-dependencies]
129137
criterion = { version = "0.8.2", features = ["html_reports"] }
130138
# Test-only: load captured-response fixtures and their sidecar .meta.toml

0 commit comments

Comments
 (0)