Repository navigation
fix(app): settings saves from the UI are written by a worker, coalesced, and flushed at exit (#2916) - #2930
Merged
Conversation
…ed, and flushed at exit (#2916) UserConfig::saveInBackground() hands a copy of the settings to a CoalescingWriter, a one-shot ts-config-save std::jthread (the ts-battery-read pattern): at most one write in flight, a newer snapshot replaces one not written yet, and a run of failed writes is logged once. The CPU Details toggle, Settings' Save, the privilege notice's "don't show again", the remembered dump folder and TASKSMACK_DEMO's theme= step now save through it, so the UI thread never writes config.toml. save() stays synchronous for the exit path (ShellLayer::onDetach) and Settings' first-run Edit config file, and first flushes the writer, so a background write can never land over the final settings. load() and resetConfigPathForTesting() flush it too; the file write and its merge base (m_Synced) are serialised by a mutex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
mgradwohl
enabled auto-merge (squash)
October 25, 2026 13:14
…he save worker's thread name (#2916) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-worker # Conflicts: # src/App/ElevationNoticeLayer.cpp
Owner
Author
|
Merged main to clear the conflict in ElevationNoticeLayer.cpp: main moved the suppress step into suppressFutureNotices(), so that helper now calls saveInBackground(). Also fixed the red Fuzz changed parsers job: the user-config fuzzer links UserConfig.cpp, whose save worker now calls Platform::setCurrentThreadName(), so .clusterfuzzlite/build.sh adds src/Platform/ThreadName.cpp to that fuzzer. |
… throw, as the other noexcept loggers do (#2916) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Clicking a setting no longer writes
config.tomlon the UI thread (AGENTS.md: "the UI thread ... never ... does file I/O"; this was point 4b of the #2731 audit).App/CoalescingWriter.h(new, header-only): fire-and-forget writes of a snapshot. It reuses thets-battery-readpattern:submit()starts a one-shotstd::jthreadwhen none is running, and that thread writes until nothing is pending, then ends, so no thread sits idle between saves. At most one write is in flight. A snapshot submitted while one is being written replaces any earlier snapshot not yet written, so rapid clicks cost one more write, holding the last state. A failed write (one that returns false or throws) is reported once per run of failures, and reported again only after a write has succeeded.flush()and the destructor write anything still pending before they return. If no thread can be started (std::system_error), the snapshot stays pending and the nextsubmit()orflush()writes it.UserConfig::saveInBackground()(new): copies the settings and submits the copy to thets-config-savewriter (newPlatform::SETTINGS_SAVE_THREAD_NAME). The worker logsSaved config to <path> on ts-config-save. A run of failures logs oneSettings weren't saved ...warning, and each failure's own reason is still logged.UserConfig::save()stays synchronous but flushes the writer first. That way a background write still in flight can't rename an older snapshot over the final settings, so the last state always wins and nothing is lost.load()andresetConfigPathForTesting()flush it too. The file write and its merge base (m_Synced) are now serialised bym_WriteMutex, andm_IsLoadedis atomic because a background write resets it.UI-thread call sites of
save(), and what each one does now:SystemMetricsPanelCPU Details toggle (the issue's case)saveInBackground()SettingsLayer::applySettings()(Settings' Save)saveInBackground()ElevationNoticeLayer"don't show again"saveInBackground()DumpDetail::rememberDumpDirectory()(a chosen dump folder)saveInBackground()DemoLayertheme=stepsaveInBackground(). Before, it saved only at exit, despite the comment "As Settings' Apply: saved with the settings"ShellLayer::onDetach()(exit)save(), unchanged. Nothing draws after it, and the flush inside it runs firstSampling is untouched.
Closes #2916
PR Size
Medium: 654 lines added, 28 removed. Most of that is new and well commented:
CoalescingWriter.his 186 lines and its tests are 270. The changes to existing code come to about 130 lines, most of them inUserConfig.cppandUserConfig.h.Type of Change
Checklist
clang-tidyand addressed any warningsclang-formaton my changespre-commit run --all-filesor installed pre-commit hooksTesting
tests/App/test_CoalescingWriter.cpp(7 tests, fake sink):flush()and destruction write a pending snapshot.UserConfigLoadSaveTestcases:load()waits for a background save;save()after a background save writes the last state;test_ProcessDumpViewandtest_SettingsLayerRenderflush before checking the file on disk.readability-trailing-comma,readability-redundant-nested-if,bugprone-signed-bitwise, and the like.TASKSMACK_CONFIG_DIR=<scratch>,TASKSMACK_DEMO="2:theme=dracula",TASKSMACK_SCREENSHOT=<png>;after=5.Theme changed to dracula, thenSaved config to ...\config.toml on ts-config-save2 ms later, then the exit path's synchronousSaved config to ...\config.toml.config.tomlholds[theme] id = 'dracula', and the screenshot renders in Dracula.Additional Notes
save()does the flush itself, so every synchronous caller gets the same guarantee..github/copilot-instructions.md's list of thread names now includests-config-save.🤖 Generated with Claude Code