Simplify the Beast fatal error reporting framework:

* Reduce interface to a single function which reports error details
* Remove unused functions
This commit is contained in:
Nik Bougalis
2014-11-16 23:17:42 -08:00
parent 933a98b97c
commit 756ac603db
6 changed files with 75 additions and 389 deletions

View File

@@ -18,111 +18,58 @@
//==============================================================================
#include <beast/module/core/diagnostic/FatalError.h>
#include <beast/unit_test/suite.h>
#include <atomic>
#include <exception>
#include <iostream>
#include <mutex>
namespace beast {
//
// FatalError::Reporter
//
void FatalError::Reporter::onFatalError (
char const* message,
char const* backtrace,
char const* filePath,
int lineNumber)
{
reportMessage (formatMessage (message, backtrace, filePath, lineNumber));
}
void FatalError::Reporter::reportMessage (std::string const& message)
{
std::cerr << message << std::endl;
}
std::string FatalError::Reporter::formatMessage (
char const* message,
char const* backtrace,
char const* filePath,
int lineNumber)
{
std::string output;
output.reserve (16 * 1024);
output.append (message);
if (filePath != nullptr && filePath [0] != 0)
{
output.append (", in ");
output.append (formatFilePath (filePath));
output.append (" line ");
output.append (std::to_string (lineNumber));
}
output.append ("\n");
if (backtrace != nullptr && backtrace[0] != 0)
{
output.append ("Stack:\n");
output.append (backtrace);
}
return output;
}
std::string FatalError::Reporter::formatFilePath (char const* filePath)
{
return filePath;
}
//------------------------------------------------------------------------------
FatalError::Reporter *FatalError::s_reporter;
/** Returns the current fatal error reporter. */
FatalError::Reporter* FatalError::getReporter ()
void
FatalError (char const* message, char const* file, int line)
{
return s_reporter;
}
static std::atomic <int> error_count (0);
static std::recursive_mutex gate;
FatalError::Reporter* FatalError::setReporter (Reporter* reporter)
{
Reporter* const previous (s_reporter);
s_reporter = reporter;
return previous;
}
// We only allow one thread to report a fatal error. Other threads that
// encounter fatal errors while we are reporting get blocked here.
std::lock_guard<std::recursive_mutex> lock(gate);
FatalError::FatalError (char const* message, char const* fileName, int lineNumber)
{
typedef CriticalSection LockType;
// If we encounter a recursive fatal error, then we want to terminate
// unconditionally.
if (error_count++ != 0)
return std::terminate ();
static LockType s_mutex;
std::lock_guard <LockType> lock (s_mutex);
auto const backtrace = SystemStats::getStackBacktrace ();
Reporter* const reporter = s_reporter;
if (reporter != nullptr)
// We protect this entire block of code since writing to cerr might trigger
// exceptions.
try
{
reporter->onFatalError (message, backtrace.c_str (), fileName, lineNumber);
std::cerr << "An error has occurred. The application will terminate.\n";
if (message != nullptr && message [0] != 0)
std::cerr << "Message: " << message << '\n';
if (file != nullptr && file [0] != 0)
std::cerr << " File: " << file << ":" << line << '\n';
auto const backtrace = SystemStats::getStackBacktrace ();
if (!backtrace.empty ())
{
std::cerr << " Stack:" << std::endl;
for (auto const& frame : backtrace)
std::cerr << " " << frame << '\n';
}
}
catch (...)
{
// nothing we can do - just fall through and terminate
}
Process::terminate ();
return std::terminate ();
}
//------------------------------------------------------------------------------
class FatalError_test : public unit_test::suite
{
public:
void run ()
{
int shouldBeZero (1);
check_invariant (shouldBeZero == 0);
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(FatalError,beast_core,beast);
} // beast

View File

@@ -32,128 +32,11 @@ namespace beast
would be to protect data integrity, prevent valuable resources from being
wasted, or to ensure that the user does not experience undefined behavior.
This function will end the process with exit code EXIT_FAILURE. Before
the process is terminated, a listener object gets notified so that the
client application can perform logging or emit further diagnostics.
If multiple threads raise an error, only one will succeed while the others
will be blocked before the process terminates.
*/
class FatalError
{
public:
struct Reporter
{
virtual ~Reporter() = default;
/** Called when a fatal error is raised.
Because the program is likely in an inconsistent state, it is a
good idea to do as little as possible from within this function.
It will be called from the thread that raised the fatal error.
The default implementation of this function first calls
formatMessage to produce the string, then calls reportMessage
to report the results.
You can override this to perform custom formatting.
@note filePath may be a zero length string if identifying
information was stripped from the executable for security.
@note stackBacktrace will be a string with zero characters for
platforms for which which don't support stack crawls, or
when symbolic information is missing from the executable.
@param message The error message.
@param stackBackTrace The stack of the thread that raised the error.
@param filePath A full or partial path to the source file that raised the error.
@param lineNumber The line number in the source file.
*/
virtual void onFatalError (char const* message,
char const* backtrace,
char const* filePath,
int lineNumber);
/** Called to report the message.
The default implementation simply writes this to standard error.
You can override this to perform additional things like logging
to a file or sending the message to another process.
@param formattedMessage The message to report.
*/
virtual void reportMessage (std::string const& formattedMessage);
protected:
/** Called to format the message.
The default implementation calls formatFilePath to produce
a formatted file name, and then creates a suitable string
containing all of the information.
You can override this function to format your own messages.
@param message The message from the report.
@param stackBacktrace The stack backtrace from the report.
@param filePath The file path from the report.
@param lineNumber The line number from the report
*/
virtual std::string formatMessage (
char const* message,
char const* backtrace,
char const* filePath,
int lineNumber);
/** Call to reformat the file path.
Usually the file is a full path, which we really don't care
to see and can also be a security hole.
The default implementation removes most of the useless
directory components from the front.
You can override this to do a custom format on the file path.
*/
virtual std::string formatFilePath (char const* filePath);
};
/** Returns the current fatal error reporter. */
static Reporter* getReporter ();
/** Set the fatal error reporter.
Note that if a fatal error is raised during the construction of
objects with static storage duration, it might not be possible to
set the reporter before the error is raised. The solution is not
to use objects with static storage duration that have non-trivial
constructors, use SharedSingleton instead.
The default behavior when no reporter is set is to invoke
the base class version of Reporter::onFatalError.
If a reporter was previously set, this routine will do nothing.
@return The previous Reporter (Which may be null).
@see SharedSingleton, Reporter
*/
static Reporter* setReporter (Reporter* reporter);
/** Raise a fatal error.
If multiple threads raise an error, only one will succeed. The
other threads will be blocked before the process terminates.
@param message A null terminated string, which should come from a constant.
@param filePath Pass __FILE__ here.
@param lineNumber Pass __LINE__ here.
*/
FatalError (char const* message, char const* filePath, int lineNumber);
FatalError(FatalError const&) = delete;
FatalError& operator= (FatalError const&) = delete;
private:
static Reporter* s_reporter;
};
void
FatalError (char const* message, char const* file = nullptr, int line = 0);
} // beast

View File

@@ -21,6 +21,10 @@
*/
//==============================================================================
#include <cstdlib>
#include <iterator>
#include <memory>
// Some basic tests, to keep an eye on things and make sure these types work ok
// on all platforms.
@@ -48,20 +52,22 @@ SystemStats::getBeastVersion()
}
//==============================================================================
std::string
std::vector <std::string>
SystemStats::getStackBacktrace()
{
std::string result;
std::vector <std::string> result;
#if BEAST_ANDROID || BEAST_MINGW || BEAST_BSD
#if BEAST_ANDROID || BEAST_MINGW || BEAST_BSD
bassertfalse; // sorry, not implemented yet!
#elif BEAST_WINDOWS
#elif BEAST_WINDOWS
HANDLE process = GetCurrentProcess();
SymInitialize (process, nullptr, TRUE);
void* stack[128];
int frames = (int) CaptureStackBackTrace (0, numElementsInArray (stack), stack, nullptr);
int frames = (int) CaptureStackBackTrace (0,
std::distance(std::begin(stack), std::end(stack)),
stack, nullptr);
HeapBlock<SYMBOL_INFO> symbol;
symbol.calloc (sizeof (SYMBOL_INFO) + 256, 1);
@@ -74,7 +80,9 @@ SystemStats::getStackBacktrace()
if (SymFromAddr (process, (DWORD64) stack[i], &displacement, symbol))
{
result.append (std::to_string (i) + ": ");
std::string frame;
frame.append (std::to_string (i) + ": ");
IMAGEHLP_MODULE64 moduleInfo;
zerostruct (moduleInfo);
@@ -82,35 +90,33 @@ SystemStats::getStackBacktrace()
if (::SymGetModuleInfo64 (process, symbol->ModBase, &moduleInfo))
{
result.append (moduleInfo.ModuleName);
result.append (": ");
frame.append (moduleInfo.ModuleName);
frame.append (": ");
}
result.append (symbol->Name);
frame.append (symbol->Name);
if (displacement)
{
result.append ("+");
result.append (std::to_string (displacement));
frame.append ("+");
frame.append (std::to_string (displacement));
}
result.append ("\r\n");
result.push_back (frame);
}
}
#else
#else
void* stack[128];
int frames = backtrace (stack, numElementsInArray (stack));
char** frameStrings = backtrace_symbols (stack, frames);
int frames = backtrace (stack,
std::distance(std::begin(stack), std::end(stack)));
std::unique_ptr<char*[], void(*)(void*)> frame (
backtrace_symbols (stack, frames), std::free);
for (int i = 0; i < frames; ++i)
{
result.append (frameStrings[i]);
result.append ("\n");
}
::free (frameStrings);
#endif
result.push_back (frame[i]);
#endif
return result;
}

View File

@@ -48,7 +48,8 @@ namespace SystemStats
The usefulness of the result will depend on the level of debug symbols
that are available in the executable.
*/
std::string getStackBacktrace();
std::vector <std::string>
getStackBacktrace();
/** A void() function type, used by setApplicationCrashHandler(). */
typedef void (*CrashHandlerFunction)();