Crash guard: prevent loading of preset if it has caused crashes.
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -1595,6 +1596,8 @@ namespace pipedal
|
||||
throw PiPedalStateException("Unable to start ALSA capture.");
|
||||
}
|
||||
|
||||
CrashGuardLock crashGuardLock;
|
||||
|
||||
cpuUse.SetStartTime(cpuUse.Now());
|
||||
while (true)
|
||||
{
|
||||
|
||||
@@ -206,6 +206,7 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
CrashGuard.cpp CrashGuard.hpp
|
||||
ModTemplateGenerator.cpp ModTemplateGenerator.hpp
|
||||
WebServerMod.cpp WebServerMod.hpp
|
||||
ModGui.cpp ModGui.hpp
|
||||
@@ -767,6 +768,7 @@ add_executable(pipedal_latency_test
|
||||
DummyAudioDriver.cpp DummyAudioDriver.hpp
|
||||
JackConfiguration.hpp JackConfiguration.cpp
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
CrashGuard.cpp CrashGuard.hpp
|
||||
CpuUse.hpp
|
||||
CpuUse.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "CrashGuard.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include <fstream>
|
||||
#include <Lv2Log.hpp>
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int CrashGuard::crashCount = 0;
|
||||
std::mutex CrashGuard::mutex;
|
||||
std::filesystem::path CrashGuard::fileName;
|
||||
int64_t CrashGuard::depth = 0;
|
||||
|
||||
void CrashGuard::SetCrashGuardFileName(const std::filesystem::path &path)
|
||||
{
|
||||
CrashGuard::fileName = path;
|
||||
|
||||
{
|
||||
std::ifstream f{path};
|
||||
if (f.is_open())
|
||||
{
|
||||
f >> crashCount;
|
||||
}
|
||||
if (crashCount > 0) {
|
||||
Lv2Log::info("CrashGuard: Detected previous crash, count = %d", (int)crashCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool CrashGuard::HasCrashed()
|
||||
{
|
||||
return crashCount > 4;
|
||||
}
|
||||
void CrashGuard::ClearCrashFlag()
|
||||
{
|
||||
crashCount = 0;
|
||||
try
|
||||
{
|
||||
if (fs::exists(fileName))
|
||||
{
|
||||
fs::remove(fileName);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void CrashGuard::EnterCrashGuardZone()
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
if (depth++ == 0)
|
||||
{
|
||||
if (!fileName.empty()) {
|
||||
ofstream_synced f{fileName};
|
||||
f << (crashCount + 1) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
void CrashGuard::LeaveCrashGuardZone()
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
if (--depth == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fileName.empty() && fs::exists(fileName))
|
||||
{
|
||||
fs::remove(fileName);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Crash protection.
|
||||
*
|
||||
* Detect the situation where PiPedal has crashed multiple times due to SIG_SEGVS caused by a plugin.
|
||||
*
|
||||
* The basic premise: mark segments of code as "crash protected". If Pipedal shuts down without exiting the
|
||||
* crash guard zone for some fixed number of occasions (3?), then set a flag that will prevent
|
||||
* loading of the default plugin when PiPedal next loads. Since PiPedal restarts indefinitely while
|
||||
* running as a service, this allows users to recover after they have committed a plugin that crashes
|
||||
* into their current preset.
|
||||
*
|
||||
*/
|
||||
|
||||
class CrashGuard
|
||||
{
|
||||
public:
|
||||
static void SetCrashGuardFileName(const std::filesystem::path &path);
|
||||
static bool HasCrashed();
|
||||
static void ClearCrashFlag();
|
||||
|
||||
static void EnterCrashGuardZone();
|
||||
static void LeaveCrashGuardZone();
|
||||
|
||||
private:
|
||||
static int crashCount;
|
||||
static std::mutex mutex;
|
||||
static std::filesystem::path fileName;
|
||||
static int64_t depth;
|
||||
};
|
||||
|
||||
class CrashGuardLock {
|
||||
public:
|
||||
CrashGuardLock() {
|
||||
CrashGuard::EnterCrashGuardZone();
|
||||
}
|
||||
~CrashGuardLock() {
|
||||
CrashGuard::LeaveCrashGuardZone();
|
||||
}
|
||||
CrashGuardLock(const CrashGuardLock &) = delete;
|
||||
CrashGuardLock &operator=(const CrashGuardLock &) = delete;
|
||||
CrashGuardLock(CrashGuardLock &&) = delete;
|
||||
CrashGuardLock &operator=(CrashGuardLock &&) = delete;
|
||||
};
|
||||
};
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <stdexcept>
|
||||
#include "ss.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -287,6 +288,7 @@ namespace pipedal
|
||||
|
||||
bool ok = true;
|
||||
|
||||
CrashGuardLock crashGuardLock;
|
||||
while (true)
|
||||
{
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "AudioHost.hpp"
|
||||
#include "Lv2EventBufferWriter.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -401,6 +402,8 @@ void Lv2Pedalboard::UpdateAudioPorts()
|
||||
|
||||
void Lv2Pedalboard::Activate()
|
||||
{
|
||||
CrashGuardLock crashGuardLock;
|
||||
|
||||
for (int i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
this->realtimeEffects[i]->Activate();
|
||||
|
||||
+25
-10
@@ -45,6 +45,7 @@
|
||||
#include "AvahiService.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "AudioFiles.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
@@ -271,20 +272,33 @@ void PiPedalModel::Load()
|
||||
|
||||
this->pedalboard = storage.GetCurrentPreset(); // the current *saved* preset.
|
||||
|
||||
|
||||
|
||||
// the current edited preset, saved only across orderly shutdowns.
|
||||
CurrentPreset currentPreset;
|
||||
try
|
||||
{
|
||||
if (storage.RestoreCurrentPreset(¤tPreset))
|
||||
|
||||
CrashGuard::SetCrashGuardFileName(storage.GetDataRoot() / "crash_guard.data");
|
||||
|
||||
if (CrashGuard::HasCrashed()) {
|
||||
// ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash.
|
||||
this->pedalboard = Pedalboard::MakeDefault();
|
||||
} else {
|
||||
CurrentPreset currentPreset;
|
||||
try
|
||||
{
|
||||
this->pedalboard = currentPreset.preset_;
|
||||
this->hasPresetChanged = currentPreset.modified_;
|
||||
if (storage.RestoreCurrentPreset(¤tPreset))
|
||||
{
|
||||
this->pedalboard = currentPreset.preset_;
|
||||
this->hasPresetChanged = currentPreset.modified_;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to load current preset. " << e.what()));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to load current preset. " << e.what()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
UpdateDefaults(&this->pedalboard);
|
||||
|
||||
std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(pluginHost.asIHost())};
|
||||
@@ -2610,6 +2624,7 @@ void PiPedalModel::SetSelectedPedalboardPlugin(uint64_t clientId, uint64_t pedal
|
||||
|
||||
bool PiPedalModel::LoadCurrentPedalboard()
|
||||
{
|
||||
CrashGuardLock crashGuardLock;
|
||||
if (previousPedalboardLoaded && pedalboard.IsStructureIdentical(previousPedalboard))
|
||||
{
|
||||
// then we can send a snapshot update instead!
|
||||
|
||||
Reference in New Issue
Block a user