Repository navigation
MQTTManager: wait for the web interface's database lock instead of failing - #399
Merged
tpanajott merged 1 commit intoOct 16, 2026
Merged
Conversation
…iling
The web interface triggers a reload on every save, so a reload's reads often
run while that write is still committing. MQTTManager opened SQLite without a
busy timeout, so those reads failed at once with "database is locked", the
settings getters returned empty strings, and NSPanel's config builder aborted
the process on std::stoi(""). Deleting a light and then adding another in quick
succession was enough to trigger it.
Give every connection a 20 s busy timeout, matching the web interface's.
`database` is a static in a header, so each translation unit has its own
storage; the timeout is set where each copy is built rather than in init(),
which only ever configured main.cpp's copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
|
Nice one! I've been chasing these issues for a while but never been able to reliably reproduce them when trying to debug them. Thanks! Also, having only main open_forever is not the intended functionality. I need to do some tests where that is not the case. |
4 tasks
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.
Problem
The web interface tells MQTTManager to reload on every save, so a reload's reads often run while the write that triggered it is still committing. MQTTManager opens SQLite without a busy timeout, so those reads fail at once:
The settings getters log the error and return
"". The typed getters turn that into 0, so a reload that loses the race quietly sends panels config built from zeros (button timings and so on).NSPanel's config builder callsstd::stoionscreen_dim_leveland similar settings directly, and on""that aborts the process. Deleting a light in the web interface and adding another straight afterwards was enough to trigger it.Django already waits for locks:
settings.pysets"timeout": 20. MQTTManager was the side that didn't wait.Fix
Every connection MQTTManager opens now gets a 20 s busy timeout (
sqlite3_busy_timeout), matching Django's.database_manager::databaseis astatic inlinevariable in a header, so each translation unit that includesdatabase_manager.hpphas its own storage and its own connections. A timeout set ininit()would only reachmain.cpp's copy. So the timeout is set throughon_openin amake_database()helper that builds each copy. That is also whydatabase.open_forever()ininit()only ever applies tomain.cpp's copy, and every other translation unit opens a connection per query. This PR leaves that as it is, but it may not be what was intended.Testing
A new test,
tests/database_test.cpp, has a second connection take the write lock (BEGIN EXCLUSIVE), as the web interface does during a save, and release it after 500 ms. It then checks that a read throughdatabase_manager::databasewaits for the lock rather than throwing.database is locked.I ran this test on its own, built against the dev Conan cache. I have not run the full
run_tests.shsuite on this branch.The crash was found by running the panel firmware in QEMU against the dev stack. The firmware's integration tests add and remove a light, and delete a panel, in quick succession.
Not in this PR
std::stoicalls innspanel.cpp, so a bad or missing setting can't abort the process.run_uwsgi.shcopies only the main database file, so it would need changing too.🤖 Generated with Claude Code