Crash guard: prevent loading of preset if it has caused crashes.

This commit is contained in:
Robin E. R. Davies
2025-07-21 12:09:27 -04:00
parent b60fbd18f0
commit 7f8b272504
8 changed files with 211 additions and 12 deletions
+1 -2
View File
@@ -9,9 +9,8 @@ following conditions:
- Must be remotely controllable (no hard dependency on GUI-only controls), which is true of all but a tiny minority of LV2 plugins.
If you install a new LV2 plugin, PiPedal will detect the change and make it available immediately. Wait a few seconds, and the newly-installed plugin should show up in the list of available plugins.
PiPedal does not currently allow plugins to provide custom user interfaces; it displays controls that are declared by the LV2 plugin without customization. In practice, this means that LV2 plugins with simple sets of controls work well, but those with complex sets of controls can be difficult to use. However, we would be pleased to collaborate with developers who need more than the basic set of controls. This is a feature set that Pipedal needs to address, so we would be happy to collaborate with other developers on this. Please contact rerdavies at gmail.com for further details.
If you install a new LV2 plugin, PiPedal will detect the change and make it available immediately. Wait a few seconds, and the newly-installed plugin should show up in the list of available plugins.
--------
[<< Using LV2 Audio Plugins](UsingLv2Plugins.md) | [Up](Documentation.md) | [MOD UI Support >>](ModUiSupport.md)
+3
View File
@@ -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)
{
+2
View File
@@ -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
)
+99
View File
@@ -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)
{
}
}
}
+76
View File
@@ -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;
};
};
+2
View File
@@ -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)
{
+3
View File
@@ -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
View File
@@ -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(&currentPreset))
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(&currentPreset))
{
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!