Skip to content

feat(sdk): bind the slow-callback watchdog across every binding#820

Merged
userFRM merged 2 commits into
mainfrom
feat/slow-callback-binding-parity
Jun 16, 2026
Merged

feat(sdk): bind the slow-callback watchdog across every binding#820
userFRM merged 2 commits into
mainfrom
feat/slow-callback-binding-parity

Conversation

@userFRM

@userFRM userFRM commented Jun 16, 2026

Copy link
Copy Markdown
Owner

What

The slow-callback watchdog ships on both core streaming surfaces — the unified Stream view and the standalone StreamingClient — but reached none of the Python, TypeScript, C++, or C-ABI bindings, and sdks/parity.toml carried no row for it. This brings it to full cross-binding parity, on both surfaces, mirroring the existing dropped_event_count observability metric exactly.

Surface

slow_callback_count() (getter) and the threshold setter are now exposed on every binding, on both the unified streaming view and the standalone client:

  • C ABI: thetadatadx_client_slow_callback_count / thetadatadx_client_set_slow_callback_threshold_us and the thetadatadx_streaming_* pair (forwarding into the same core accessors every higher binding wraps).
  • Python: client.stream.slow_callback_count() / set_slow_callback_threshold_us(...) and the same pair on the standalone StreamingClient, plus .pyi stubs.
  • TypeScript: slowCallbackCount() / setSlowCallbackThresholdUs(...) on StreamView and StreamingClient, plus .d.ts declarations.
  • C++: slow_callback_count() / set_slow_callback_threshold_us(...) on the unified Stream and the standalone StreamingClient.

The threshold crosses the boundary as microseconds: callback budgets are commonly sub-millisecond and the core watchdog measures wall-clock at nanosecond resolution, so the _us suffix matches the convention every other duration-valued knob already follows. The watchdog is observability-only — it counts over-budget invocations and logs (rate-limited), and never cancels or kills the callback.

Guard blind spot

scripts/check_binding_parity.py gated each declared parity row against the bindings but never gated the reverse direction on the Rust side, so an observability accessor wired onto the core streaming surface with no parity row reached no binding and tripped no check — exactly how this gap existed. Added a reverse-direction scan that harvests the public observability accessors (cumulative counters, ring telemetry, the threshold setter) on the core StreamSurface / StreamingClient impls and asserts each carries a [[method]] row. Covered by four new selftest cases (positive all-enrolled, unenrolled getter, unenrolled setter, internal-hook excluded). Also scoped the TypeScript Config setter-set collector to impl Config blocks so a live-surface setter is not mistaken for a Config knob.

C++ counter rename

The standalone StreamingClient exposed dropped_events() while the unified Stream exposed dropped_event_count() for the same counter. Renamed the StreamingClient C++ wrapper method to dropped_event_count() on both surfaces (the underlying C symbol is unchanged) and collapsed the two parity rows into one.

Tests

  • Python: tests/test_slow_callback.py (lifecycle + round-trip on the unified view), offline assertions added to tests/test_standalone_clients.py.
  • TypeScript: __tests__/slow_callback.test.mjs plus the surface roster in __tests__/fpss_client.test.mjs.
  • C++: STATIC_REQUIRE type-trait checks in tests/fpss.cpp and tests/thetadatadx_client.cpp, plus a live round-trip line.

Verification

  • python3 scripts/check_binding_parity.py — clean
  • python3 scripts/check_binding_parity.py --selftest — 74 passed, 0 failed
  • python3 scripts/test_check_binding_parity.py — all 27 cases passed
  • python3 scripts/check_c_abi_completeness.py — clean (323 symbols, bidirectional parity)
  • cargo fmt --all -- --check — clean
  • cargo build -p thetadatadx-ffi --release — clean; thetadatadx-py and thetadatadx-napi compile; C++ headers pass g++ -fsyntax-only

🤖 Generated with Claude Code

The slow-callback watchdog ships on both core streaming surfaces — the unified Stream view and the standalone StreamingClient — but reached none of the Python, TypeScript, C++, or C-ABI bindings. A counter that operators cannot read from the language they call the SDK in cannot do its job. Expose slow_callback_count() and the threshold setter on every binding, on both surfaces, so the observability contract is identical regardless of the language the user reached for.

The threshold crosses the boundary as microseconds (set_slow_callback_threshold_us / setSlowCallbackThresholdUs) because callback budgets are commonly sub-millisecond and the core watchdog measures wall-clock at nanosecond resolution; the unit suffix matches the convention every other duration-valued knob already follows (wait_park_us, the keepalive seconds knobs). The watchdog is observability-only: it counts over-budget invocations and logs, and never cancels or kills the callback.

New C symbols thetadatadx_client_slow_callback_count / thetadatadx_client_set_slow_callback_threshold_us and the thetadatadx_streaming_* pair forward into the same core accessors every higher binding wraps. The Python pyo3 methods, the napi methods, the C++ forwarders, the .pyi / .d.ts stubs, and the C header declarations all mirror the existing dropped_event_count placement byte-for-byte.

Close the guard blind spot that let this gap exist: scripts/check_binding_parity.py gated each declared parity row against the bindings but never gated the reverse direction on the Rust side, so an observability accessor wired onto the core streaming surface with no parity row reached no binding and tripped no check. Add a reverse-direction scan that harvests the public observability accessors (cumulative counters, ring telemetry, the threshold setter) on the core StreamSurface and StreamingClient impls and asserts each carries a [[method]] row, with selftest coverage for the positive, unenrolled-getter, unenrolled-setter, and internal-hook-excluded cases. Scope the TypeScript Config setter-set collector to impl Config blocks so a live-surface setter is no longer mistaken for a Config knob.

Unify the C++ counter spelling: the standalone StreamingClient exposed dropped_events() while the unified Stream exposed dropped_event_count() for the same counter. Rename the StreamingClient wrapper method to dropped_event_count() on both surfaces (the underlying C symbol is unchanged) so the C++ surface reads consistently, and collapse the two parity rows into one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@userFRM userFRM enabled auto-merge (squash) June 16, 2026 13:34
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@userFRM userFRM merged commit 0f5218f into main Jun 16, 2026
42 of 43 checks passed
@userFRM userFRM deleted the feat/slow-callback-binding-parity branch June 16, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants