Skip to content

fix(store): bound read_topics XREAD count by default - #3950

Open
ryanmelt wants to merge 1 commit into
mainfrom
fix/read-topics-count
Open

ryanmelt wants to merge 1 commit into
mainfrom
fix/read-topics-count

Conversation

@ryanmelt

Copy link
Copy Markdown
Member

What

  • Store#read_topics (Ruby) and StoreImplementation.read_topics (Python) now default count to READ_TOPICS_DEFAULT_COUNT (1000 entries per stream) when the caller passes none. The positional signature is unchanged, so existing plugin callers keep working.
  • cmd-tlm-api StreamingThread, LoggedStreamingThread, TopicsThread and MessagesThread pass an explicit count equal to their batch size, so each XREAD reads at most one batch per stream.
  • StreamingThread / LoggedStreamingThread now advance object offsets past stored packets they skip. Before, offsets only moved on realtime packets. With a capped read, a run of stored packets longer than the count would have been re-read forever.

Why

XREAD with no COUNT returns every entry from the consumer's offset to the end of the stream, and the client holds the whole reply in memory before the caller's block runs. With high-rate telemetry, a consumer that falls behind can pull hundreds of thousands of entries in one call, which spikes the process's memory (RSS). In Ruby that memory is often never returned to the OS. A bounded read turns a burst like that into a series of normal-sized reads.

Caller audit

I checked every read_topics caller in core Ruby/Python, cmd-tlm-api, script-runner-api and Enterprise. All of them either loop and continue from the offsets tracked per thread, or pass and update explicit offsets. A capped read therefore just continues on the next iteration. get_packets, ConfigTopic.read and LimitsEventTopic.read already passed their own count. The one caller that could stall was the realtime streaming path skipping stored packets without advancing offsets; that is fixed here.

Testing

  • New specs: openc3/spec/utilities/store_spec.rb (default cap and explicit count) and openc3-cosmos-cmd-tlm-api/spec/models/streaming_thread_spec.rb (count passed through; offsets advance past stored packets).
  • New pytest: test/utilities/test_store_implementation.py::TestStoreReadTopics.
  • openc3 rspec on utilities, topics, api, models and microservices: 1893 examples. The 2 failures also fail on main (local AWS credential issue).
  • cmd-tlm-api spec/models: 210 examples, 0 failures.
  • Python: the full suite has the same set of failures before and after this change (local environment); ruff check is clean.

🤖 Generated with Claude Code

XREAD without COUNT returns everything from the consumer's offset to the
stream tail, so a consumer that falls behind on high-rate telemetry pulls
an unbounded reply into memory at once. Default to 1000 entries per stream
and have the cmd-tlm-api streaming threads read at most one batch per
stream. Streaming threads now advance offsets past skipped stored packets
so a capped read cannot stall re-reading them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
9.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.14%. Comparing base (688f928) to head (829e4db).

Files with missing lines Patch % Lines
...-cmd-tlm-api/app/models/logged_streaming_thread.rb 40.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3950      +/-   ##
==========================================
+ Coverage   80.08%   80.14%   +0.06%     
==========================================
  Files         901      901              
  Lines       68356    68367      +11     
  Branches     2645     2698      +53     
==========================================
+ Hits        54743    54793      +50     
+ Misses      12946    12908      -38     
+ Partials      667      666       -1     
Flag Coverage Δ
frontend 67.02% <ø> (+0.07%) ⬆️
python 80.13% <ø> (-0.01%) ⬇️
ruby-api 82.65% <81.25%> (+0.65%) ⬆️
ruby-backend 85.65% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

patcher.start()
self.addCleanup(patcher.stop)
for i in range(5):
EphemeralStore.write_topic("TEST__TOPIC", {"i": str(i)})

This branch has not been deployed

No deployments
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.

1 participant