fix(cli): stage generated config files before commit - #318
Conversation
There was a problem hiding this comment.
🟡 Not ready to approve
Current cleanup behavior can delete an existing valid target config file on write/rename errors, which risks data loss when overwriting an existing timestamped output.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR hardens hocon_cli config generation against disk-full (ENOSPC) scenarios by switching to temp-file + rename writes and adding cleanup logic so failed writes don’t leave corrupted config artifacts that can break subsequent node startups.
Changes:
- Replace direct
file:write_file/2writes inhocon_cli:generate/1withatomic_write_file/2(write to*.tmp, then rename). - Add cleanup logic to remove temporary/failed-write artifacts.
- Add a unit test covering write failure cleanup behavior.
File summaries
| File | Description |
|---|---|
src/hocon_cli.erl |
Introduces atomic_write_file/2 and cleanup behavior; updates generator to use atomic writes. |
test/hocon_cli_tests.erl |
Adds a regression test ensuring failed atomic writes don’t leave files behind. |
Review details
Suppressed comments (1)
src/hocon_cli.erl:418
maybe_log_file_error/2currently callscleanup_file(Filename), which deletes the final target file on error. After switching to atomic temp-file writes, this can remove a previously valid config/vm.args file when an overwrite fails; it should only try to remove the temp file left behind byatomic_write_file/2.
maybe_log_file_error(Filename, {error, Reason}) ->
cleanup_file(Filename),
log(error, "Error writing ~s: ~s", [Filename, file:format_error(Reason)]),
ok.
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
Follow-up commit
Local OTP 27/28 xref + full EUnit pass (563 tests each), OTP 27 Dialyzer passes, and OTP 26 style checks pass. The three GitHub Actions runs are currently marked |
| _ = file:delete(Filename), | ||
| maybe_log_rollback_error(Filename, file:rename(BackupFilename, Filename)) |
There was a problem hiding this comment.
just log the error.
file:rename/1 atomically rename and overwrite existing.
attempting to delete will fail wit the same error, and retry won't help.
There was a problem hiding this comment.
Agreed. The delete/retry path was removed in 2883fc7. Follow-up 1939178 also separates delete cleanup from restore failures, so an enoent from the backup rename is logged instead of being silently treated like an already-deleted new target. The regression test leaves the current target untouched and now exercises the logged error path.
|
Follow-up commit
Added regression coverage for older timestamps, stale temp cleanup, and rollback failure preservation. Verified in clean OTP 27 and OTP 28 containers: 22 Please re-review the latest commit when convenient. |
29d6e88 to
2883fc7
Compare
|
The branch was updated to Final clean OTP 28 verification: Please review the latest head |
zmstone
left a comment
There was a problem hiding this comment.
thank you, last two comments.
| HistoryLimit = | ||
| case MaxHistory > 0 of | ||
| true -> MaxHistory - 1; | ||
| false -> 0 | ||
| end, |
There was a problem hiding this comment.
Nit: this is HistoryLimit = max(MaxHistory - 1, 0).
1939178 to
c01c610
Compare
|
Addressed the latest review feedback in
Validation after rebase:
|
|
I found and fixed two additional filesystem edge cases in The new tests cover those cases plus second-target preparation failure. I reran clean OTP 23/26/27/28 checks and three real 1 MiB tmpfs ENOSPC scenarios; OTP 27 and 28 full EUnit both pass with 584 tests. The PR description has the exact current flow and validation results. |
|
please rebase (and squash) |
Stage and sync both generated outputs before publishing either final file. Preserve existing targets with rollback copies, and restore or remove partial commits on handled rename failures. Clean only matching stale staging files, and prune history before and after commit so ENOSPC retries can reclaim space without deleting the current generation. Add regression coverage for staging, rollback, pruning, cleanup, permissions, and replacement failures.
1302f5f to
936e64a
Compare
|
Rebased onto latest Verification after the rewrite:
No GitHub checks are reported yet for the rewritten head. |
|
thank you for the pr. |
Two robustness fixes in emqx_config_backup_manager: 1. When reading the current file for backup fails with a non-enoent error, the staged file is now still renamed into place. The backup is best-effort (its write failures are already only warnings), while persisting the new config is the actual contract of backup_and_write; skipping one backup generation is preferable to silently dropping the new config while reporting success. 2. The staged tmp file is now synced to disk before the atomic rename, so a power loss right after the rename cannot leave an empty or truncated cluster.hocon. Mirrors the staging/sync/rename pattern adopted in emqx/hocon#318 for the generated app/vm files.
Problem
When disk space is exhausted (
ENOSPC), writing generatedapp.<time>.configandvm.<time>.argsdirectly can create or truncate final files before the write fails. A later startup may then pick up a 0-byte or incomplete generated configuration.Failure-path review also found that earlier revisions could delete an existing target, publish only one of the two generated files, accumulate abandoned temp files, prune the just-written generation, or fail to reclaim excess history before an ENOSPC retry.
Solution
sync, and close every temporary file before committing either output.vm.argsfirst andapp.configlast.--dest_filecannot prune another config family.This provides rollback for handled staging/rename errors. It does not claim a strict two-file ACID transaction across process crashes, concurrent generators, or power loss.
Regression tests
Coverage includes:
Testing
rebar3 compilepassedhocon_cli_testspassed — 25 testsgit diff --checkpassed