Crash guard: prevent loading of preset if it has caused crashes.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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