mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-26 06:00:17 +00:00
Add BGMTermination files forgotten in previous commit.
This commit is contained in:
parent
59e70fb9d1
commit
db63ae0cf1
2 changed files with 297 additions and 0 deletions
86
BGMApp/BGMApp/BGMTermination.h
Normal file
86
BGMApp/BGMApp/BGMTermination.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
// This file is part of Background Music.
|
||||
//
|
||||
// Background Music is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// Background Music is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//
|
||||
// BGMTermination.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
// Cleans up if BGMApp crashes because of an uncaught C++ or Objective C exception, or is sent
|
||||
// SIGINT/SIGTERM/SIGQUIT. Currently, it just changes the default output device from BGMDevice to
|
||||
// the real output device and records debug info for some types of crashes.
|
||||
//
|
||||
// BGMXPCHelper also changes the default device if BGMApp disconnects and leaves BGMDevice as the
|
||||
// default. This handles cases like segfaults where it wouldn't be safe to clean up from the
|
||||
// crashing process.
|
||||
//
|
||||
|
||||
#ifndef BGMApp__BGMTermination
|
||||
#define BGMApp__BGMTermination
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CAPThread.h"
|
||||
|
||||
// STL Includes
|
||||
#import <exception>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGMTermination
|
||||
{
|
||||
|
||||
public:
|
||||
/*!
|
||||
Starts a thread that will clean up before exiting if BGMApp receives SIGINT, SIGTERM or
|
||||
SIGQUIT. Sets a similar clean up function to run if BGMApp terminates due to an uncaught
|
||||
exception.
|
||||
*/
|
||||
static void SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevices);
|
||||
|
||||
/*! Some commented out ways to have BGMApp crash for testing. Does nothing if unmodified. */
|
||||
static void TestCrash() __attribute__((noinline));
|
||||
|
||||
private:
|
||||
static void StartExitSignalsThread();
|
||||
|
||||
static void CleanUpAudioDevices();
|
||||
|
||||
/*! Adds some info about the uncaught exception that caused a crash to the crash report. */
|
||||
static void AddCurrentExceptionToCrashReport();
|
||||
|
||||
/*! The entry point for sExitSignalsThread. */
|
||||
static void* __nullable ExitSignalsProc(void* __nullable ignored);
|
||||
|
||||
/*! The thread that handles SIGQUIT, SIGTERM and SIGINT. Never destroyed. */
|
||||
static CAPThread* const sExitSignalsThread;
|
||||
static sigset_t sExitSignals;
|
||||
|
||||
/*! The function that handles std::terminate by default. */
|
||||
static std::terminate_handler sOriginalTerminateHandler;
|
||||
|
||||
/*! The audio device manager. (Must be static to be accessed in our std::terminate_handler.) */
|
||||
static BGMAudioDeviceManager* __nullable sAudioDevices;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMApp__BGMTermination */
|
||||
|
211
BGMApp/BGMApp/BGMTermination.mm
Normal file
211
BGMApp/BGMApp/BGMTermination.mm
Normal file
|
@ -0,0 +1,211 @@
|
|||
// This file is part of Background Music.
|
||||
//
|
||||
// Background Music is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// Background Music is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//
|
||||
// BGMTermination.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMTermination.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CADebugMacros.h"
|
||||
|
||||
// STL Includes
|
||||
#import <string>
|
||||
|
||||
// System Includes
|
||||
#import <signal.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
std::terminate_handler BGMTermination::sOriginalTerminateHandler = std::get_terminate();
|
||||
|
||||
CAPThread* const BGMTermination::sExitSignalsThread = new CAPThread(ExitSignalsProc, nullptr);
|
||||
sigset_t BGMTermination::sExitSignals;
|
||||
|
||||
BGMAudioDeviceManager* __nullable BGMTermination::sAudioDevices = nullptr;
|
||||
|
||||
// If BGMApp crashes, CrashReporter will read this string from our process' memory and include it in
|
||||
// the crash report.
|
||||
const char* __nullable __crashreporter_info__ = nullptr;
|
||||
// Set the REFERENCED_DYNAMICALLY bit so the symbol doesn't get stripped from the binary. See
|
||||
// <https://developer.apple.com/library/content/documentation/DeveloperTools/Reference/Assembler/040-Assembler_Directives/asm_directives.html>
|
||||
// and
|
||||
// <https://github.com/aidansteele/osx-abi-macho-file-format-reference#symbol-table-and-related-data-structures>
|
||||
// (Ctrl+F "REFERENCED_DYNAMICALLY").
|
||||
asm(".desc ___crashreporter_info__, 0x10");
|
||||
|
||||
|
||||
// static
|
||||
void BGMTermination::TestCrash()
|
||||
{
|
||||
// To give BGMApp a few seconds to finish launching and then crash:
|
||||
//
|
||||
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
|
||||
// dispatch_get_main_queue(),
|
||||
// ^{
|
||||
// BGMTermination::TestCrash();
|
||||
// });
|
||||
|
||||
// throw CAException(kAudioHardwareBadDeviceError);
|
||||
// throw BGM_InvalidClientRelativeVolumeException();
|
||||
// std::string().at(1);
|
||||
// *reinterpret_cast<int*>(0x1234) = 9;
|
||||
// [NSException raise:@"ObjC Test Exception" format:@"The description of the test exception."];
|
||||
}
|
||||
|
||||
// static
|
||||
void BGMTermination::SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevices)
|
||||
{
|
||||
sAudioDevices = inAudioDevices;
|
||||
|
||||
StartExitSignalsThread();
|
||||
|
||||
// Wrap the default handler for std::terminate, which is called if BGMApp crashes because of an
|
||||
// uncaught C++ or Objective C exception, so we can clean up first.
|
||||
sOriginalTerminateHandler = std::get_terminate();
|
||||
|
||||
std::set_terminate([] {
|
||||
CleanUpAudioDevices();
|
||||
|
||||
AddCurrentExceptionToCrashReport();
|
||||
|
||||
// Call the default terminate handler to finish crashing normally.
|
||||
sOriginalTerminateHandler();
|
||||
});
|
||||
}
|
||||
|
||||
// static
|
||||
void BGMTermination::StartExitSignalsThread()
|
||||
{
|
||||
// Block the signals the thread will handle, so they can be unblocked for just that thread.
|
||||
sigemptyset(&sExitSignals);
|
||||
sigaddset(&sExitSignals, SIGQUIT);
|
||||
sigaddset(&sExitSignals, SIGTERM);
|
||||
sigaddset(&sExitSignals, SIGINT);
|
||||
|
||||
if(pthread_sigmask(SIG_BLOCK, &sExitSignals, nullptr) != 0)
|
||||
{
|
||||
perror("pthread_sigmask");
|
||||
return; // This would just mean the signals would be handled by the default handlers.
|
||||
}
|
||||
|
||||
// Start the thread.
|
||||
sExitSignalsThread->Start();
|
||||
}
|
||||
|
||||
// static
|
||||
void BGMTermination::CleanUpAudioDevices()
|
||||
{
|
||||
// BGMXPCHelper would set the output device back if we didn't do it here, but in general
|
||||
// it's better for things to work even if BGMXPCHelper isn't installed.
|
||||
if(sAudioDevices)
|
||||
{
|
||||
[sAudioDevices unsetBGMDeviceAsOSDefault];
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void BGMTermination::AddCurrentExceptionToCrashReport()
|
||||
{
|
||||
std::exception_ptr exceptionPtr = std::current_exception();
|
||||
|
||||
if(exceptionPtr)
|
||||
{
|
||||
// The message to add to the crash report (and log).
|
||||
std::string* msg = new std::string("");
|
||||
|
||||
// Throw the exception again and catch it so we can get some info if it's a CAException. If
|
||||
// it's a std::exception, the default terminate handler will do the same thing, so we can
|
||||
// just ignore it here.
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(exceptionPtr);
|
||||
}
|
||||
catch(const CAException& e)
|
||||
{
|
||||
OSStatus err = e.GetError();
|
||||
const char err4CC[5] = CA4CCToCString(err);
|
||||
|
||||
msg = new std::string("Uncaught CAException. Error code: '");
|
||||
msg->append(err4CC);
|
||||
msg->append("' (");
|
||||
msg->append(std::to_string(err));
|
||||
msg->append(").");
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
|
||||
// CrashReporter will read the contents of __crashreporter_info__.
|
||||
__crashreporter_info__ = msg->c_str();
|
||||
NSLog(@"%s", msg->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// The entry point for the thread that handles SIGQUIT, SIGTERM and SIGINT.
|
||||
// static
|
||||
void* __nullable BGMTermination::ExitSignalsProc(void* __nullable ignored)
|
||||
{
|
||||
#pragma unused (ignored)
|
||||
|
||||
DebugMsg("BGMTermination::ExitSignalsProc: Thread started.");
|
||||
|
||||
int signal = -1;
|
||||
|
||||
// Wait until we receive a signal.
|
||||
while((signal != SIGINT) && (signal != SIGTERM) && (signal != SIGQUIT))
|
||||
{
|
||||
if(sigwait(&sExitSignals, &signal) != 0)
|
||||
{
|
||||
perror("sigwait");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if(signal == SIGINT)
|
||||
{
|
||||
NSLog(@"Interrupted.");
|
||||
}
|
||||
else if(signal == SIGTERM)
|
||||
{
|
||||
NSLog(@"Exiting.");
|
||||
}
|
||||
|
||||
CleanUpAudioDevices();
|
||||
|
||||
// Unblock the signal and resend it to ourselves so it will be handled by the default handler
|
||||
// and exit BGMApp.
|
||||
if(pthread_sigmask(SIG_UNBLOCK, &sExitSignals, nullptr) != 0)
|
||||
{
|
||||
perror("pthread_sigmask");
|
||||
abort();
|
||||
}
|
||||
|
||||
raise(signal);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
Loading…
Reference in a new issue