Fix ScheduleManager start() self-deadlock on cached schedule

start() held mutex_ and then called loadCache(), which re-locks the same
non-recursive mutex. When a schedule cache file exists and parses (the
normal case for a provisioned kiosk), this self-deadlocks and blocks
SDL_AppInit before the first frame, leaving only a black screen and the
compositor cursor.

Narrow the lock to the member setup so loadCache() can take the lock
itself. Add a regression test that starts with a cached payload present.
This commit is contained in:
NeilClack
2026-10-07 08:43:13 -05:00
parent a5a6b5bef0
commit 9b57e48969
2 changed files with 41 additions and 9 deletions
+13 -9
View File
@@ -87,16 +87,20 @@ void ScheduleManager::refreshOnce() {
void ScheduleManager::start(db::DatabaseManager& db, const std::string& timezone,
std::function<void()> onChanged) {
std::lock_guard<std::mutex> lock(mutex_);
if (started_) return;
started_ = true;
db_ = &db;
timezone_ = timezone;
onChanged_ = std::move(onChanged);
licenseKey_ = db.getKV(db::KV_LICENSE_KEY);
enabled_ = true; // config supplies the toggle via setEnabled()
{
std::lock_guard<std::mutex> lock(mutex_);
if (started_) return;
started_ = true;
db_ = &db;
timezone_ = timezone;
onChanged_ = std::move(onChanged);
licenseKey_ = db.getKV(db::KV_LICENSE_KEY);
enabled_ = true; // config supplies the toggle via setEnabled()
}
// Offline first: load any cached payload immediately.
// Offline first: load any cached payload immediately. Called outside the
// lock because loadCache() acquires mutex_ itself; holding it here would
// self-deadlock on the non-recursive mutex when a cache file exists.
loadCache();
stop_.store(false);