Skip to content

Fix race condition corrupting cfg files when updating an existing configuration - #2897

Merged
jbonofre merged 3 commits into
apache:mainfrom
jbonofre:GH-2805-fix-config-file-race-condition
Sep 14, 2026
Merged

jbonofre merged 3 commits into
apache:mainfrom
jbonofre:GH-2805-fix-config-file-race-condition

Conversation

@jbonofre

Copy link
Copy Markdown
Member

Summary

Fixes #2805.

FeatureConfigInstaller.updateExistingConfig() (used both for the append and override config flows) read the existing cfg file, merged in the new properties in memory, then wrote the result directly back to the target file (properties.save(cfgFile) / Configurations.buildWriter().build(new FileWriter(cfgFile))...).

Calling cfg.update(...) just before that write also triggers Configuration Admin to notify its persistence manager (fileinstall), which persists the very same configuration to the very same cfg file on the CM Event Dispatcher thread. With two unsynchronized, non-atomic writers hitting the same file at once, the writes can interleave and corrupt the file (as shown in the issue, where lines from the old file content bleed into the newly written content).

The "file doesn't exist yet" path already avoids this by writing to a temp file and renaming it atomically into place (KARAF-7389 / #1489), but that fix didn't cover the "file already exists" path exercised by append/override. This PR applies the same temp-file + atomic rename pattern to updateExistingConfig(), for both the properties and JSON formats, so a concurrent reader/writer can only ever observe a fully-old or fully-new file, never a torn one.

Test plan

  • mvn -pl features/core test — all existing tests pass (139 tests)
  • Added FeatureConfigInstallerTest#testUpdateExistingConfigWritesAtomically, which exercises updateExistingConfig() against a pre-existing cfg file and asserts the merged content is correct and no leftover .tmp file remains after the rename

…figuration

FeatureConfigInstaller.updateExistingConfig() wrote directly to the target
cfg file when appending to / overriding an existing configuration. Since
cfg.update() also triggers fileinstall to persist the same configuration
on the CM Event Dispatcher thread, two unsynchronized writers could hit the
same file at once, producing torn/corrupted content. The "new file" path
already avoided this by writing to a temp file and renaming it atomically
into place (KARAF-7389); this applies the same pattern to the
existing-file path.

Fixes apache#2805
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown

Test Results

  723 files  ±0    723 suites  ±0   1h 16m 36s ⏱️ - 5m 17s
  982 tests +1    934 ✅ +1   48 💤 ±0  0 ❌ ±0 
2 946 runs  +3  2 802 ✅ +3  144 💤 ±0  0 ❌ ±0 

Results for commit 741ff3b. ± Comparison against base commit e637cf5.

♻️ This comment has been updated with latest results.

…enameTo

File.renameTo() silently fails (returns false, no exception) on Windows
when the destination already exists, leaving the cfg file stale and the
temp file orphaned. This broke updateExistingConfig() since it is only
called when the target file already exists, causing the Windows CI job
to fail (AppendTest).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Atomic-move portability, file-permission preservation, and regression-test effectiveness remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds atomic temporary-file replacement to prevent configuration corruption during concurrent writes.

Changes:

  • Replaces direct cfg writes with atomic file moves.
  • Adds a regression-oriented test for existing properties files.
File summaries
File Description
FeatureConfigInstaller.java Writes new and updated configurations through temporary files.
FeatureConfigInstallerTest.java Tests merged properties and temporary-file cleanup.
Review details

Suppressed comments (1)

features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java:418

  • This second unconditional ATOMIC_MOVE has the same portability failure: an unsupported provider leaves the existing file stale after the in-memory Configuration Admin update, while the exception is merely logged. Add the established AtomicMoveNotSupportedException fallback here as well.
        Files.move(tmpCfgFile.toPath(), cfgFile.toPath(),
                StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@jbonofre
jbonofre merged commit 772fcec into apache:main Sep 14, 2026
7 checks passed
@jbonofre
jbonofre deleted the GH-2805-fix-config-file-race-condition branch September 14, 2026 13:20
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.

Race condition on initial Karaf start corrupts config file

2 participants