feat(sdk): bind the slow-callback watchdog across every binding#820
Merged
Conversation
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>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The slow-callback watchdog ships on both core streaming surfaces — the unified
Streamview and the standaloneStreamingClient— but reached none of the Python, TypeScript, C++, or C-ABI bindings, andsdks/parity.tomlcarried no row for it. This brings it to full cross-binding parity, on both surfaces, mirroring the existingdropped_event_countobservability 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:thetadatadx_client_slow_callback_count/thetadatadx_client_set_slow_callback_threshold_usand thethetadatadx_streaming_*pair (forwarding into the same core accessors every higher binding wraps).client.stream.slow_callback_count()/set_slow_callback_threshold_us(...)and the same pair on the standaloneStreamingClient, plus.pyistubs.slowCallbackCount()/setSlowCallbackThresholdUs(...)onStreamViewandStreamingClient, plus.d.tsdeclarations.slow_callback_count()/set_slow_callback_threshold_us(...)on the unifiedStreamand the standaloneStreamingClient.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
_ussuffix 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.pygated 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 coreStreamSurface/StreamingClientimpls 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 toimpl Configblocks so a live-surface setter is not mistaken for a Config knob.C++ counter rename
The standalone
StreamingClientexposeddropped_events()while the unifiedStreamexposeddropped_event_count()for the same counter. Renamed theStreamingClientC++ wrapper method todropped_event_count()on both surfaces (the underlying C symbol is unchanged) and collapsed the two parity rows into one.Tests
tests/test_slow_callback.py(lifecycle + round-trip on the unified view), offline assertions added totests/test_standalone_clients.py.__tests__/slow_callback.test.mjsplus the surface roster in__tests__/fpss_client.test.mjs.STATIC_REQUIREtype-trait checks intests/fpss.cppandtests/thetadatadx_client.cpp, plus a live round-trip line.Verification
python3 scripts/check_binding_parity.py— cleanpython3 scripts/check_binding_parity.py --selftest— 74 passed, 0 failedpython3 scripts/test_check_binding_parity.py— all 27 cases passedpython3 scripts/check_c_abi_completeness.py— clean (323 symbols, bidirectional parity)cargo fmt --all -- --check— cleancargo build -p thetadatadx-ffi --release— clean;thetadatadx-pyandthetadatadx-napicompile; C++ headers passg++ -fsyntax-only🤖 Generated with Claude Code