Skip to content

Astrobud atomic save - #166

Merged
PastorD merged 10 commits into
masterfrom
astrobud-atomic-save
Aug 31, 2026
Merged

Astrobud atomic save#166
PastorD merged 10 commits into
masterfrom
astrobud-atomic-save

Conversation

@PastorD

@PastorD PastorD commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Fix some issues with save positions we had in Astrobud. We had save positions fail few times. It does not happen every time so it is hard to track. Once, it created the file but it was empty, meaning the process was killed in after it created the file but before it saved to file. In addition, previous save position keep invalid file while the arm was moving, and the positions would be lost if power was lost or the processs was forcefully closed. This PR minimizes the time we lose calibration to only when we lose power while the arm is moving.

  • Writes the position file atomically, so it's never corrupt or missing.
  • Saves when the arm comes to a stop (all brakes engaged) and invalidates the file while moving — the file only ever holds a verified-stopped snapshot. Better to have no positions than wrong ones.
  • Guarantees a save on clean shutdown.
  • Moves all disk I/O to a background thread so a save can't cause a control-loop cycle slip.

This has been tested for hundreds of commands in Astrobud, initially killing the node to check behaviour was as expected, and then sometimes watching the saved positions file was destroyed and created.

Some edges cases that might require a different platform to test:

  • the code is written for both platinum and gold, but only tested with gold actuators because that is the only type we have in Astrobud
  • what happens if the arm does not have a brakes engaged signal (no motor_on field)?

@nasa-jpl/nasa-jpl-ethercat-devs

PastorD added 3 commits July 30, 2026 21:49
…Positions()

- SaveActuatorPosFile now writes to a temp file, fsyncs, backs up the current
  canonical file via copy (not rename), then atomically renames temp over the
  canonical file. The canonical fastcat_saved_positions.yaml is never left
  missing or partially written, even if killed mid-save.
- Add public SaveActuatorPositions() that locks parameter_mutex_ (shared with
  Process()) then captures + writes positions, so it is safe to call while the
  process loop is still running (e.g. from a ROS pre-shutdown callback).
- Shutdown() now routes through SaveActuatorPositions().
- Add AllBrakesEngaged() (all incremental GOLD/PLATINUM actuators motor_on==0).
- UpdatePositionFileOnBrakeState() runs at end of Process() (under lock):
  rising edge (motion stopped, brakes engaged) saves positions; falling edge
  (motion started) invalidates (removes) the canonical file so stale positions
  are never loaded.
- InvalidateActuatorPosFile() removes fastcat_saved_positions.yaml, keeps _prev.
- SaveActuatorPositions() (shutdown path) now gated on AllBrakesEngaged(): a
  save requested mid-motion invalidates instead of writing in-motion positions.
The brake-edge save ran fsync/rename/backup-copy synchronously on the RT
Process() thread under parameter_mutex_, causing a ~60ms (6-cycle @100hz)
cycle slip. Split the save:
- BuildActuatorPosYaml(): cheap serialization on the RT thread under the lock.
- WritePosFileToDisk(): all disk I/O, runs only on a dedicated writer thread.
A single-slot coalescing mailbox (mutex+cv) hands the pre-built string to the
writer; RT thread never blocks on disk. Brake edges are fire-and-forget; the
shutdown save waits for the writer to drain (durability preserved). fsync and
_prev backup are kept, just off the RT path. Writer starts in InitHardware,
stops/joins in ~Manager. Falls back to inline write if writer not running.
@PastorD PastorD self-assigned this Aug 12, 2026
@PastorD
PastorD requested a review from JosephBowkett August 12, 2026 16:30
  #1 — Position-file bypass now honored on the save/invalidate paths

  Added pos_file_enabled_ (manager.h:324), set in LoadActuatorPosFile() only after both bypass checks pass (manager.cc:1148), and checked at the top of SaveActuatorPositions() and
  UpdatePositionFileOnBrakeState(). The bug was real and I reproduced it: with the pre-fix binary, a no-actuator topology that logged "bypassing saved positions file functions" went on to
  delete /tmp/fastcat_saved_positions.yaml anyway — first during InitHardware()'s Process() calls, then again on Shutdown(). Post-fix the sentinel file survives untouched with no
  invalidate logged.

  #2 — RT loop no longer stalls on fsync

  SaveActuatorPositions() (manager.cc:106) now releases parameter_mutex_ before blocking on the writer. Getting this right needed one more step than I first wrote: my initial version
  released the lock before posting, which opened a window where the RT loop could post its falling-edge invalidate first and have our later write supersede it — leaving a file full of
  pre-motion positions while the arm was actually moving. That reintroduces exactly the staleness the invalidate exists to prevent. The final version posts under parameter_mutex_ (cheap —
  mailbox mutex only, no disk) and defers only the wait, so ordering against the brake edges is preserved and the RT thread never blocks on I/O.
  PostPosWriteRequest/PostPosInvalidateRequest now return the request sequence, and a new WaitForPosWriter(seq) does the blocking.

  #3 — Header doc corrected

  manager.h:47 now states plainly that this must not be called from a signal handler, explains why (mutex + allocation aren't async-signal-safe; self-deadlock if the signal lands on the
  thread already inside Process()), and points to the flag-and-poll pattern test_cli.cc already uses.

  #5 — umask no longer clobbered process-wide

  Both umask(000) calls are gone. Files are chmod'd individually to kPosFileMode (0666) — the temp file before the rename, so the canonical file lands with final permissions already set
  and is never briefly unwritable. Verified with the shell umask at 022: a pre-existing 644 file was replaced by a 666 one, and the _prev backup is 666 too. The comment claiming umask is
  per-thread was wrong and is now corrected in place.

@JosephBowkett JosephBowkett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but lacks description of the new parameter actuator_position_save_settle_sec inside doc/fastcat_device_config_parameters.md

StartPosWriter() seems to set the running flag before the thread actually runs, which might create an edge case if caught between those two things. Should probably flip the order

@PastorD

PastorD commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

both issues fixed

  • added actuator_position_save_settle_sec to docs
  • setting pos_writer_running_ after thread is actually created. Make it atomic to block compiler from changing the order

@PastorD
PastorD merged commit 0819d64 into master Aug 31, 2026
4 checks passed
@PastorD
PastorD deleted the astrobud-atomic-save branch August 31, 2026 22:10
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