Skip to content

fix: persist the cursor file after every advancing poll cycle - #175

Merged
Valreb001 merged 2 commits into
Tx-wats:mainfrom
ayomidearegbeshola29-dev:fix/issue-5-cursor-writeback
Sep 30, 2026
Merged

Valreb001 merged 2 commits into
Tx-wats:mainfrom
ayomidearegbeshola29-dev:fix/issue-5-cursor-writeback

Conversation

@ayomidearegbeshola29-dev

Copy link
Copy Markdown
Contributor

Closes #5.

Problem

AppConfig::cursor_file is documented as "write the updated cursor map after each poll cycle", and the poller reads it on startup — but nothing ever wrote it. save_cursors was called only from run_once, so the continuous engine never persisted anything.

After a restart the poller reloaded whatever cursor was in the file originally (or now), so transactions were either replayed from a stale cursor, producing duplicate alerts, or skipped entirely.

Change

  • write_cursor_file writes a temp file in the same directory, fsyncs it, renames it over the target, and fsyncs the directory. Neither a half-written file nor a lost rename can survive a crash. Previously this was fs::write + rename with no fsync at all.
  • Each contract's task shares the cursor map and, after a cycle that advanced its own cursor, updates the shared map and writes the file under the lock — so concurrent tasks cannot clobber each other's entry.
  • The cursor is flushed again when the tasks are drained, so a graceful shutdown persists anything advanced since the last per-cycle write.
  • A poisoned lock recovers its contents rather than taking the poller down.
  • Writes only happen when a cursor actually advanced, so an idle poller does not rewrite the file every cycle.

poll_contract_forever now reads its starting cursor from the shared map, so it no longer needs it passed in.

Testing

The new integration test drives the real polling loop, waits for the cursor file to contain the paging token the mock actually served, and asserts the temp file is not left behind.

It fails on the parent commit with cursor file never contained the served paging token after its 15s deadline, so it is testing the fix rather than the pre-existing behaviour.

  • cargo fmt --all -- --check
  • cargo clippy -p txwatch-poller --all-targets --all-features
  • cargo test -p txwatch-poller — 33 lib + 16 integration + attack tests, all passing

Note on the diff

This branch stacks on the build repair in #169, which is not merged yet, so the repair commits are included. They are unrelated to this fix and can be dropped once #169 lands.

Closes Tx-wats#5.

`AppConfig::cursor_file` is documented as "write the updated cursor map
after each poll cycle", and the poller reads it on startup — but nothing
ever wrote it. `save_cursors` was only called from `run_once`, so the
continuous engine never persisted anything. After a restart the poller
reloaded whatever cursor was in the file originally (or `now`), so
transactions were either replayed from a stale cursor, producing
duplicate alerts, or skipped entirely.

- `write_cursor_file` writes a temp file in the same directory, fsyncs it,
  renames it over the target and fsyncs the directory, so neither a
  half-written file nor a lost rename can survive a crash. Previously
  `fs::write` + `rename` with no fsync at all.
- Each contract's task shares the cursor map and, after a cycle that
  advanced its own cursor, updates the shared map and writes the file
  under the lock, so concurrent tasks cannot clobber each other's entry.
- The cursor is flushed again when the tasks are drained, so a graceful
  shutdown persists anything advanced since the last per-cycle write.
- A poisoned lock recovers its contents rather than taking the poller down.

`poll_contract_forever` picks its starting cursor from the shared map, so
it no longer needs the cursor passed in.

The new integration test drives the real polling loop and fails on the
parent commit with "cursor file never contained the served paging token"
after its 15s deadline; it also asserts the temp file is not left behind.
@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@ayomidearegbeshola29-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Valreb001
Valreb001 merged commit 7bc8d62 into Tx-wats:main Sep 30, 2026
1 of 10 checks passed
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.

cursor_file is loaded on startup but never written back

2 participants