diff --git a/CHANGELOG.md b/CHANGELOG.md index ef13d5f5..f45ac53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.25] - 2026-05-05 + +### Fixed + +- **Windows `ERROR_IO_PENDING` (os error 997) no longer trips a fatal + FPSS read error.** On Windows the overlapped socket layer surfaces + in-flight reads as `ERROR_IO_PENDING` instead of `WSAEWOULDBLOCK`. + Rust `std` maps raw OS error 997 to `ErrorKind::Uncategorized`, so + the existing `WouldBlock | TimedOut` matches in + `crates/thetadatadx/src/fpss/io_loop.rs::is_read_timeout` and the + two retry arms in `crates/thetadatadx/src/fpss/framing.rs` + (pre-header and mid-payload) treated it as fatal — Python users on + Windows saw `FPSS read error error=IO error: Overlapped I/O + operation is in progress. (os error 997)` spam followed by a + reconnect storm. A new `is_transient_read` helper in `framing.rs` + matches `WouldBlock`, `TimedOut`, and `raw_os_error() == Some(997)`; + all three sites delegate to it so the I/O loop drains queued + commands and retries the way it does on Linux and macOS. + Closes #469. + +### Changed + +- `tdbe` 0.12.5 → 0.12.7. + ## [8.0.24] - 2026-05-04 ### Added diff --git a/Cargo.lock b/Cargo.lock index 2740742b..ff14660a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3022,7 +3022,7 @@ dependencies = [ [[package]] name = "tdbe" -version = "0.12.5" +version = "0.12.6" dependencies = [ "criterion", "thiserror 2.0.18", @@ -3043,7 +3043,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" dependencies = [ "arrow-array", "arrow-schema", @@ -3082,7 +3082,7 @@ dependencies = [ [[package]] name = "thetadatadx-cli" -version = "8.0.24" +version = "8.0.25" dependencies = [ "clap", "comfy-table", @@ -3095,7 +3095,7 @@ dependencies = [ [[package]] name = "thetadatadx-ffi" -version = "8.0.24" +version = "8.0.25" dependencies = [ "tdbe", "thetadatadx", diff --git a/crates/tdbe/Cargo.toml b/crates/tdbe/Cargo.toml index b463e6ed..b4aefde3 100644 --- a/crates/tdbe/Cargo.toml +++ b/crates/tdbe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tdbe" -version = "0.12.5" +version = "0.12.6" edition.workspace = true rust-version.workspace = true authors.workspace = true diff --git a/crates/thetadatadx/Cargo.toml b/crates/thetadatadx/Cargo.toml index 72493079..22235ece 100644 --- a/crates/thetadatadx/Cargo.toml +++ b/crates/thetadatadx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" edition.workspace = true rust-version.workspace = true authors.workspace = true @@ -40,7 +40,7 @@ frames = ["polars", "arrow"] live-tests = [] [dependencies] -tdbe = { version = "0.12.5", path = "../tdbe" } +tdbe = { version = "0.12.6", path = "../tdbe" } # gRPC + protobuf (tonic 0.14 extracted prost codec into tonic-prost) tonic = { version = "=0.14.5", features = ["tls-ring", "tls-native-roots", "channel", "transport"] } @@ -132,7 +132,7 @@ prost-build = "=0.14.3" regex = "1.12.3" toml = "1.1.2" serde = { version = "1.0.228", features = ["derive"] } -tdbe = { version = "0.12.5", path = "../tdbe" } +tdbe = { version = "0.12.6", path = "../tdbe" } [[bench]] name = "bench_decode" diff --git a/crates/thetadatadx/src/fpss/framing.rs b/crates/thetadatadx/src/fpss/framing.rs index 19788b57..bb8f4135 100644 --- a/crates/thetadatadx/src/fpss/framing.rs +++ b/crates/thetadatadx/src/fpss/framing.rs @@ -31,6 +31,38 @@ use tdbe::types::enums::StreamMsgType; use super::protocol::READ_TIMEOUT_MS; +/// Windows `ERROR_IO_PENDING` raw OS error code. +/// +/// On Windows the overlapped socket layer surfaces in-flight reads as +/// `ERROR_IO_PENDING` (Win32 error 997) instead of `WSAEWOULDBLOCK`. Rust +/// `std` maps 997 to `ErrorKind::Uncategorized`, so a `kind()` match on +/// `WouldBlock | TimedOut` misses it and a benign in-flight read appears as +/// a fatal I/O error. Callers must check the `raw_os_error()` to recognise +/// it as transient. +/// +/// Reference: +pub(crate) const ERROR_IO_PENDING: i32 = 997; + +/// Classify a raw `std::io::Error` returned by `read()` as a transient +/// "no data right now, try again" condition. +/// +/// Returns `true` for the three cases the FPSS framing and I/O loops must +/// retry / drain on rather than escalate to a fatal disconnect: +/// +/// - `ErrorKind::WouldBlock` — Linux, macOS `SO_RCVTIMEO` on a non-blocking +/// socket. +/// - `ErrorKind::TimedOut` — macOS `SO_RCVTIMEO` on a blocking socket. +/// - `raw_os_error() == Some(997)` — Windows `ERROR_IO_PENDING` from the +/// overlapped I/O layer (issue #469). Maps to `ErrorKind::Uncategorized` +/// in `std`, so a `kind()` match alone misses it. +#[must_use] +pub(crate) fn is_transient_read(io_err: &std::io::Error) -> bool { + matches!( + io_err.kind(), + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut + ) || io_err.raw_os_error() == Some(ERROR_IO_PENDING) +} + /// Maximum payload length (single unsigned byte). /// /// Source: `PacketStream.java` -- the length field is one byte. @@ -231,13 +263,7 @@ fn read_header_with_timeout( }) } Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, - Err(e) - if n > 0 - && matches!( - e.kind(), - std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut - ) => - { + Err(e) if n > 0 && is_transient_read(&e) => { // Drain-yield: the aggregate wall-clock cap exists so // the command drain cannot be starved by a trickling // sender. The partial header bytes are preserved on @@ -345,12 +371,7 @@ fn read_exact_payload_with_timeout( state.payload_read = n + k; } Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, - Err(e) - if matches!( - e.kind(), - std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut - ) => - { + Err(e) if is_transient_read(&e) => { // Drain-yield: the aggregate wall-clock cap exists so // the command drain cannot be starved by a trickling // sender. The partial payload bytes are preserved @@ -1401,4 +1422,162 @@ mod tests { )); assert!(!is_drain_yield(&io)); } + + /// Windows `ERROR_IO_PENDING` (raw OS error 997) must classify as a + /// transient read. Rust `std` maps 997 to `ErrorKind::Uncategorized`, + /// so a plain `kind()` match would miss it and treat the in-flight + /// overlapped read as a fatal disconnect — which is exactly what the + /// Python user reported in issue #469. + #[test] + fn is_transient_read_recognises_windows_error_io_pending() { + let err = std::io::Error::from_raw_os_error(ERROR_IO_PENDING); + // Sanity: confirm the precondition that motivates this fix — + // `std` does not map 997 to a recognisable kind on any platform. + assert_ne!(err.kind(), std::io::ErrorKind::WouldBlock); + assert_ne!(err.kind(), std::io::ErrorKind::TimedOut); + assert_eq!(err.raw_os_error(), Some(997)); + assert!( + is_transient_read(&err), + "ERROR_IO_PENDING (os error 997) must be classified as transient" + ); + + // Other raw OS errors (e.g. ECONNRESET on Linux) must NOT be + // classified as transient — they are real disconnects. + let real_err = std::io::Error::from_raw_os_error(104); // ECONNRESET + assert!( + !is_transient_read(&real_err), + "ECONNRESET must not be classified as transient" + ); + + // The classic kinds still match. + let wb = std::io::Error::new(std::io::ErrorKind::WouldBlock, "x"); + let to = std::io::Error::new(std::io::ErrorKind::TimedOut, "x"); + assert!(is_transient_read(&wb)); + assert!(is_transient_read(&to)); + } + + /// Reader that yields a prefix, then `n_stalls` errors of the given + /// raw OS error code, then a suffix. Models a Windows TLS socket + /// surfacing `ERROR_IO_PENDING` (997) between the header and payload. + struct PrefixThenOsErrThenResume { + prefix: Vec, + suffix: Vec, + prefix_pos: usize, + suffix_pos: usize, + remaining_stalls: usize, + os_error: i32, + } + + impl std::io::Read for PrefixThenOsErrThenResume { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if self.prefix_pos < self.prefix.len() { + let remaining = &self.prefix[self.prefix_pos..]; + let n = remaining.len().min(buf.len()); + buf[..n].copy_from_slice(&remaining[..n]); + self.prefix_pos += n; + return Ok(n); + } + if self.remaining_stalls > 0 { + self.remaining_stalls -= 1; + return Err(std::io::Error::from_raw_os_error(self.os_error)); + } + if self.suffix_pos < self.suffix.len() { + let remaining = &self.suffix[self.suffix_pos..]; + let n = remaining.len().min(buf.len()); + buf[..n].copy_from_slice(&remaining[..n]); + self.suffix_pos += n; + return Ok(n); + } + Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "reader exhausted", + )) + } + } + + /// Reader that always returns a raw OS error after delivering a + /// prefix. Models a Windows socket where the read goes pending and + /// never completes within the test window. + struct AlwaysOsErrAfter { + prefix: Vec, + pos: usize, + os_error: i32, + } + + impl std::io::Read for AlwaysOsErrAfter { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if self.pos < self.prefix.len() { + let remaining = &self.prefix[self.pos..]; + let n = remaining.len().min(buf.len()); + buf[..n].copy_from_slice(&remaining[..n]); + self.pos += n; + Ok(n) + } else { + Err(std::io::Error::from_raw_os_error(self.os_error)) + } + } + } + + /// Pre-header `ERROR_IO_PENDING` (zero bytes delivered) must propagate + /// as `Error::Io` — same path `WouldBlock` takes — so + /// `io_loop::is_read_timeout` can drain queued commands and retry on + /// the next poll instead of escalating to a reconnect storm. Issue + /// #469: this is exactly the case where the Python user on Windows + /// saw `Overlapped I/O operation is in progress. (os error 997)` spam + /// followed by repeated reconnect attempts. + #[test] + fn pre_header_error_io_pending_propagates_as_io() { + let mut reader = AlwaysOsErrAfter { + prefix: Vec::new(), + pos: 0, + os_error: ERROR_IO_PENDING, + }; + let err = read_frame(&mut reader).unwrap_err(); + match err { + crate::error::Error::Io(e) => { + assert_eq!(e.raw_os_error(), Some(ERROR_IO_PENDING)); + } + other => panic!("expected Error::Io(ERROR_IO_PENDING), got {other:?}"), + } + } + + /// Mid-header `ERROR_IO_PENDING` (one byte delivered, second stalls + /// briefly with os error 997) must retry within the per-stall + /// deadline and return the complete frame. Without the fix this + /// arm fell through to `Err(e) => Err(e.into())` and surfaced as a + /// fatal `FPSS read error` to the user. + #[test] + fn mid_header_error_io_pending_retries_and_recovers() { + let mut reader = PrefixThenOsErrThenResume { + prefix: vec![0x01], + suffix: vec![StreamMsgType::Ping as u8, 0xAA], + prefix_pos: 0, + suffix_pos: 0, + remaining_stalls: 3, + os_error: ERROR_IO_PENDING, + }; + let frame = read_frame(&mut reader).unwrap().unwrap(); + assert_eq!(frame.code, StreamMsgType::Ping); + assert_eq!(frame.payload, vec![0xAA]); + } + + /// Mid-payload `ERROR_IO_PENDING` (header + partial payload, brief + /// stall with os error 997, rest arrives) must retry and complete. + /// This is the most common shape on Windows: a real frame whose + /// payload bytes finish arriving 50–76 ms after the first overlapped + /// pending notification. + #[test] + fn mid_payload_error_io_pending_retries_and_recovers() { + let mut reader = PrefixThenOsErrThenResume { + prefix: vec![0x04, StreamMsgType::Ping as u8, 0x01, 0x02], + suffix: vec![0x03, 0x04], + prefix_pos: 0, + suffix_pos: 0, + remaining_stalls: 3, + os_error: ERROR_IO_PENDING, + }; + let frame = read_frame(&mut reader).unwrap().unwrap(); + assert_eq!(frame.code, StreamMsgType::Ping); + assert_eq!(frame.payload, vec![0x01, 0x02, 0x03, 0x04]); + } } diff --git a/crates/thetadatadx/src/fpss/io_loop.rs b/crates/thetadatadx/src/fpss/io_loop.rs index ec4a9dd2..2b961c34 100644 --- a/crates/thetadatadx/src/fpss/io_loop.rs +++ b/crates/thetadatadx/src/fpss/io_loop.rs @@ -684,13 +684,17 @@ pub(super) fn io_loop( tracing::debug!("fpss-io thread exiting"); } -/// Check if an error is a read timeout (`WouldBlock` or `TimedOut`). +/// Check if an error is a transient read condition that should drain +/// commands and retry rather than tear the connection down. +/// +/// Delegates to [`super::framing::is_transient_read`] for the kind +/// classification so all three FPSS read sites (this loop, mid-header, +/// mid-payload) share one definition. Recognises `WouldBlock`, +/// `TimedOut`, and Windows `ERROR_IO_PENDING` (raw OS 997, surfaced as +/// `ErrorKind::Uncategorized` by `std`) — see issue #469. fn is_read_timeout(e: &Error) -> bool { match e { - Error::Io(io_err) => matches!( - io_err.kind(), - std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut - ), + Error::Io(io_err) => super::framing::is_transient_read(io_err), _ => false, } } diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index ef13d5f5..f45ac53b 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.25] - 2026-05-05 + +### Fixed + +- **Windows `ERROR_IO_PENDING` (os error 997) no longer trips a fatal + FPSS read error.** On Windows the overlapped socket layer surfaces + in-flight reads as `ERROR_IO_PENDING` instead of `WSAEWOULDBLOCK`. + Rust `std` maps raw OS error 997 to `ErrorKind::Uncategorized`, so + the existing `WouldBlock | TimedOut` matches in + `crates/thetadatadx/src/fpss/io_loop.rs::is_read_timeout` and the + two retry arms in `crates/thetadatadx/src/fpss/framing.rs` + (pre-header and mid-payload) treated it as fatal — Python users on + Windows saw `FPSS read error error=IO error: Overlapped I/O + operation is in progress. (os error 997)` spam followed by a + reconnect storm. A new `is_transient_read` helper in `framing.rs` + matches `WouldBlock`, `TimedOut`, and `raw_os_error() == Some(997)`; + all three sites delegate to it so the I/O loop drains queued + commands and retries the way it does on Linux and macOS. + Closes #469. + +### Changed + +- `tdbe` 0.12.5 → 0.12.7. + ## [8.0.24] - 2026-05-04 ### Added diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 9e7e2b4b..3af82b38 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-ffi" -version = "8.0.24" +version = "8.0.25" edition.workspace = true rust-version.workspace = true authors.workspace = true @@ -31,7 +31,7 @@ testing-panic-boundary = [] [dependencies] thetadatadx = { path = "../crates/thetadatadx" } -tdbe = { version = "0.12.5", path = "../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../crates/tdbe" } tokio = { version = "1.52.1", features = ["rt-multi-thread"] } # Used by the FPSS streaming callback silent-drop observability path # (see `tdx_fpss_dropped_events` / `tdx_unified_dropped_events`). Keep diff --git a/sdks/python/Cargo.lock b/sdks/python/Cargo.lock index 9948d0b0..251c283f 100644 --- a/sdks/python/Cargo.lock +++ b/sdks/python/Cargo.lock @@ -2310,7 +2310,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tdbe" -version = "0.12.5" +version = "0.12.6" dependencies = [ "thiserror 2.0.18", ] @@ -2330,7 +2330,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" dependencies = [ "disruptor", "metrics", @@ -2364,7 +2364,7 @@ dependencies = [ [[package]] name = "thetadatadx-py" -version = "8.0.24" +version = "8.0.25" dependencies = [ "arrow", "arrow-array", diff --git a/sdks/python/Cargo.toml b/sdks/python/Cargo.toml index ace2e73f..3db9503b 100644 --- a/sdks/python/Cargo.toml +++ b/sdks/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-py" -version = "8.0.24" +version = "8.0.25" edition = "2021" description = "Python bindings for thetadatadx — native ThetaData SDK powered by Rust" license = "Apache-2.0" @@ -19,7 +19,7 @@ doc = false [dependencies] # The Rust SDK we're wrapping thetadatadx = { path = "../../crates/thetadatadx" } -tdbe = { version = "0.12.5", path = "../../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../../crates/tdbe" } # Direct prost dep for decoding `thetadatadx::proto::ResponseData` bytes in # the `decode_response_bytes` hook. The main crate no longer re-exports diff --git a/sdks/typescript/Cargo.lock b/sdks/typescript/Cargo.lock index 6e5c8790..c56d7d14 100644 --- a/sdks/typescript/Cargo.lock +++ b/sdks/typescript/Cargo.lock @@ -1934,7 +1934,7 @@ dependencies = [ [[package]] name = "tdbe" -version = "0.12.5" +version = "0.12.6" dependencies = [ "thiserror 2.0.18", ] @@ -1954,7 +1954,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" dependencies = [ "disruptor", "metrics", @@ -1988,7 +1988,7 @@ dependencies = [ [[package]] name = "thetadatadx-napi" -version = "8.0.24" +version = "8.0.25" dependencies = [ "chrono", "napi", diff --git a/sdks/typescript/Cargo.toml b/sdks/typescript/Cargo.toml index 6fe42a8d..b8429555 100644 --- a/sdks/typescript/Cargo.toml +++ b/sdks/typescript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-napi" -version = "8.0.24" +version = "8.0.25" edition = "2021" description = "TypeScript/Node.js bindings for thetadatadx — native ThetaData SDK powered by Rust" license = "Apache-2.0" @@ -13,7 +13,7 @@ crate-type = ["cdylib"] [dependencies] thetadatadx = { path = "../../crates/thetadatadx" } -tdbe = { version = "0.12.5", path = "../../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../../crates/tdbe" } napi = { version = "3.8.5", features = ["async", "tokio_rt", "serde-json", "napi6", "chrono_date"] } napi-derive = "3.5.4" diff --git a/sdks/typescript/npm/darwin-arm64/package.json b/sdks/typescript/npm/darwin-arm64/package.json index be9b8ecf..27a61e2c 100644 --- a/sdks/typescript/npm/darwin-arm64/package.json +++ b/sdks/typescript/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "thetadatadx-darwin-arm64", - "version": "8.0.24", + "version": "8.0.25", "os": [ "darwin" ], diff --git a/sdks/typescript/npm/linux-x64-gnu/package.json b/sdks/typescript/npm/linux-x64-gnu/package.json index 2c64d1fd..9389d86c 100644 --- a/sdks/typescript/npm/linux-x64-gnu/package.json +++ b/sdks/typescript/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "thetadatadx-linux-x64-gnu", - "version": "8.0.24", + "version": "8.0.25", "os": [ "linux" ], diff --git a/sdks/typescript/npm/win32-x64-msvc/package.json b/sdks/typescript/npm/win32-x64-msvc/package.json index 85225ac4..b8a5593a 100644 --- a/sdks/typescript/npm/win32-x64-msvc/package.json +++ b/sdks/typescript/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "thetadatadx-win32-x64-msvc", - "version": "8.0.24", + "version": "8.0.25", "os": [ "win32" ], diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index ccb19664..c153a586 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "thetadatadx", - "version": "8.0.24", + "version": "8.0.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "thetadatadx", - "version": "8.0.24", + "version": "8.0.25", "license": "Apache-2.0", "devDependencies": { "@napi-rs/cli": "^3.6.2" @@ -15,9 +15,9 @@ "node": ">= 20" }, "optionalDependencies": { - "thetadatadx-darwin-arm64": "8.0.24", - "thetadatadx-linux-x64-gnu": "8.0.24", - "thetadatadx-win32-x64-msvc": "8.0.24" + "thetadatadx-darwin-arm64": "8.0.25", + "thetadatadx-linux-x64-gnu": "8.0.25", + "thetadatadx-win32-x64-msvc": "8.0.25" } }, "node_modules/@emnapi/core": { diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 28cbd0b0..8adeb7ad 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -1,6 +1,6 @@ { "name": "thetadatadx", - "version": "8.0.24", + "version": "8.0.25", "description": "Native ThetaData SDK for Node.js — powered by Rust via napi-rs", "license": "Apache-2.0", "repository": { @@ -30,9 +30,9 @@ "@napi-rs/cli": "^3.6.2" }, "optionalDependencies": { - "thetadatadx-linux-x64-gnu": "8.0.24", - "thetadatadx-darwin-arm64": "8.0.24", - "thetadatadx-win32-x64-msvc": "8.0.24" + "thetadatadx-linux-x64-gnu": "8.0.25", + "thetadatadx-darwin-arm64": "8.0.25", + "thetadatadx-win32-x64-msvc": "8.0.25" }, "engines": { "node": ">= 20" diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index f3ad86d9..8b061b42 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-cli" -version = "8.0.24" +version = "8.0.25" edition.workspace = true rust-version.workspace = true authors.workspace = true @@ -21,7 +21,7 @@ path = "src/main.rs" [dependencies] thetadatadx = { path = "../../crates/thetadatadx" } -tdbe = { version = "0.12.5", path = "../../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../../crates/tdbe" } json_canon = { version = "0.1.0", path = "../../crates/json_canon" } clap = { version = "4.6.1", features = ["derive"] } tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] } diff --git a/tools/mcp/Cargo.lock b/tools/mcp/Cargo.lock index c91226a4..7e452cf5 100644 --- a/tools/mcp/Cargo.lock +++ b/tools/mcp/Cargo.lock @@ -1936,7 +1936,7 @@ dependencies = [ [[package]] name = "tdbe" -version = "0.12.5" +version = "0.12.6" dependencies = [ "thiserror 2.0.18", ] @@ -1956,7 +1956,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" dependencies = [ "disruptor", "metrics", @@ -1990,7 +1990,7 @@ dependencies = [ [[package]] name = "thetadatadx-mcp" -version = "8.0.24" +version = "8.0.25" dependencies = [ "json_canon", "serde", diff --git a/tools/mcp/Cargo.toml b/tools/mcp/Cargo.toml index 8bafdfda..057f4a99 100644 --- a/tools/mcp/Cargo.toml +++ b/tools/mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-mcp" -version = "8.0.24" +version = "8.0.25" edition = "2021" description = "MCP server for ThetaDataDx — gives LLMs instant access to ThetaData market data" license = "Apache-2.0" @@ -12,7 +12,7 @@ path = "src/main.rs" [dependencies] thetadatadx = { path = "../../crates/thetadatadx" } -tdbe = { version = "0.12.5", path = "../../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../../crates/tdbe" } json_canon = { version = "0.1.0", path = "../../crates/json_canon" } tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros", "io-util", "io-std"] } serde = { version = "1.0.228", features = ["derive"] } diff --git a/tools/server/Cargo.lock b/tools/server/Cargo.lock index 7ddbac57..3db25966 100644 --- a/tools/server/Cargo.lock +++ b/tools/server/Cargo.lock @@ -2329,7 +2329,7 @@ dependencies = [ [[package]] name = "tdbe" -version = "0.12.5" +version = "0.12.6" dependencies = [ "thiserror 2.0.18", ] @@ -2349,7 +2349,7 @@ dependencies = [ [[package]] name = "thetadatadx" -version = "8.0.24" +version = "8.0.25" dependencies = [ "disruptor", "metrics", @@ -2383,7 +2383,7 @@ dependencies = [ [[package]] name = "thetadatadx-server" -version = "8.0.24" +version = "8.0.25" dependencies = [ "axum", "clap", diff --git a/tools/server/Cargo.toml b/tools/server/Cargo.toml index 78ca83e9..1a534385 100644 --- a/tools/server/Cargo.toml +++ b/tools/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thetadatadx-server" -version = "8.0.24" +version = "8.0.25" edition = "2021" rust-version = "1.85" authors = ["userFRM"] @@ -21,7 +21,7 @@ path = "src/main.rs" [dependencies] thetadatadx = { path = "../../crates/thetadatadx", features = ["config-file"] } rustls = { version = "0.23.38", features = ["ring"] } -tdbe = { version = "0.12.5", path = "../../crates/tdbe" } +tdbe = { version = "0.12.6", path = "../../crates/tdbe" } json_canon = { version = "0.1.0", path = "../../crates/json_canon" } axum = { version = "0.8.9", features = ["ws"] } tokio = { version = "1.52.1", features = ["full"] }