Skip to content

Prevent lost settings updates and partial configuration writes - #17

Merged
cchacs merged 1 commit into
mainfrom
pullfrog/14-atomic-settings-persistence
Oct 17, 2026
Merged

cchacs merged 1 commit into
mainfrom
pullfrog/14-atomic-settings-persistence

Conversation

@pullfrog

@pullfrog pullfrog Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #14.

What

my-plugin/routes.py performed an unlocked read–merge–write in asyncio.to_thread and wrote straight to <config_dir>/my-plugin.json. Concurrent subset POSTs could overwrite each other's keys, and an interrupted write could leave invalid JSON.

  • Serialized updates — an asyncio.Lock is held across the entire read–merge–persist, so overlapping subset updates observe each other's changes instead of clobbering them. Body streaming and schema validation still happen before the lock is taken, and filesystem work still runs off the event loop via asyncio.to_thread.
  • Atomic persistence — _write_settings_atomically() writes to a tempfile.mkstemp temporary file in the same directory, flushes + fsyncs it, then os.replaces it onto the destination. A failure at any point unlinks the temp file and leaves the previous configuration byte-identical (the request returns 500).
  • Tests — an overlapping-request test fires three concurrent subset POSTs to different keys and asserts all three persist; a failed-write test breaks os.replace and asserts the previous config survives unchanged with no leftover temp file. A sanity harness confirmed the overlapping test loses updates in 20/20 runs when the lock is disabled and passes with it.
  • Docs — my-plugin/README.md gains a "Concurrency and durability" section covering the guarantee, the multi-process limitation (per-process lock; cross-process locking needed for multiple workers sharing a config_dir), and the 0600 file mode that mkstemp implies; AGENTS.md and CHANGELOG.md updated to match.

The test fixture now enters TestClient as a context manager so every request shares one event loop — without that, the lock bound to the first request's per-request loop would be rejected by the second.

Verification

  • pytest -q — 21 passed (includes the two new tests)
  • CI-equivalent checks: manifest assertions, node --check my-plugin/screen.js, print-call grep — all pass

Pullfrog  | View workflow run | via Pullfrog | Using big-pickle (free) | 𝕏

Serialize the full read-merge-persist with an asyncio.Lock held across
asyncio.to_thread, and persist via a temp file in the same directory
then os.replace, so concurrent subset POSTs cannot overwrite each
other and a failed write preserves the last valid config.

Add an overlapping-request test (verified to lose updates in 20/20
runs without the lock) and a failed-write test covering config
preservation and temp-file cleanup, and document the single-process
guarantee and multi-process limitation in the README and AGENTS.md.

Closes #14
@kilo-code-bot

kilo-code-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (6 files)
  • AGENTS.md
  • CHANGELOG.md
  • my-plugin/README.md
  • my-plugin/routes.py
  • tests/conftest.py
  • tests/test_settings_routes.py

Reviewed by efficient · Input: 32.3K · Output: 10.1K · Cached: 140.7K

@cchacs
cchacs merged commit 621d5d7 into main Oct 17, 2026
9 checks passed
@cchacs
cchacs deleted the pullfrog/14-atomic-settings-persistence branch October 17, 2026 03:35
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.

Prevent lost settings updates and partial configuration writes

1 participant