1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-21 05:09:43 +00:00

Merge branch 'brainfreeze' into 'master'

Attempt to catch freezes on Windows

See merge request OpenMW/openmw!1493
This commit is contained in:
psi29a 2022-01-06 08:47:23 +00:00
commit 555e0b1f53
4 changed files with 150 additions and 6 deletions

View file

@ -1,10 +1,11 @@
#include "windows_crashcatcher.hpp"
#include <cassert>
#include <cwchar>
#include <iostream>
#include <sstream>
#include <thread>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <SDL_messagebox.h>
@ -144,6 +145,7 @@ namespace Crash
mShm->mEvent = CrashSHM::Event::Startup;
mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex);
mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess());
mShm->mStartup.mAppMainThreadId = GetThreadId(GetCurrentThread());
mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent);
mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent);
@ -196,7 +198,7 @@ namespace Crash
// must remain until monitor has finished
waitMonitor();
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !";
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
}

View file

@ -1,3 +1,5 @@
#include "windows_crashmonitor.hpp"
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@ -9,13 +11,15 @@
#include <memory>
#include <sstream>
#include <SDL_messagebox.h>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <components/debug/debuglog.hpp>
namespace Crash
{
std::unordered_map<HWINEVENTHOOK, CrashMonitor*> CrashMonitor::smEventHookOwners{};
CrashMonitor::CrashMonitor(HANDLE shmHandle)
: mShmHandle(shmHandle)
@ -28,6 +32,7 @@ namespace Crash
mShmMutex = mShm->mStartup.mShmMutex;
mAppProcessHandle = mShm->mStartup.mAppProcessHandle;
mAppMainThreadId = mShm->mStartup.mAppMainThreadId;
mSignalAppEvent = mShm->mStartup.mSignalApp;
mSignalMonitorEvent = mShm->mStartup.mSignalMonitor;
}
@ -80,6 +85,61 @@ namespace Crash
return code == STILL_ACTIVE;
}
bool CrashMonitor::isAppFrozen()
{
MSG message;
// Allow the event hook callback to run
PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE);
if (!mAppWindowHandle)
{
EnumWindows([](HWND handle, LPARAM param) -> BOOL {
CrashMonitor& crashMonitor = *(CrashMonitor*)param;
DWORD processId;
if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mAppMainThreadId && processId == GetProcessId(crashMonitor.mAppProcessHandle))
{
if (GetWindow(handle, GW_OWNER) == 0)
{
crashMonitor.mAppWindowHandle = handle;
return false;
}
}
return true;
}, (LPARAM)this);
if (mAppWindowHandle)
{
DWORD processId;
GetWindowThreadProcessId(mAppWindowHandle, &processId);
HWINEVENTHOOK eventHookHandle = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, nullptr,
[](HWINEVENTHOOK hWinEventHook, DWORD event, HWND windowHandle, LONG objectId, LONG childId, DWORD eventThread, DWORD eventTime)
{
CrashMonitor& crashMonitor = *smEventHookOwners[hWinEventHook];
if (event == EVENT_OBJECT_DESTROY && windowHandle == crashMonitor.mAppWindowHandle && objectId == OBJID_WINDOW && childId == INDEXID_CONTAINER)
{
crashMonitor.mAppWindowHandle = nullptr;
smEventHookOwners.erase(hWinEventHook);
UnhookWinEvent(hWinEventHook);
}
}, processId, mAppMainThreadId, WINEVENT_OUTOFCONTEXT);
smEventHookOwners[eventHookHandle] = this;
}
else
return false;
}
if (IsHungAppWindow)
return IsHungAppWindow(mAppWindowHandle);
else
{
BOOL debuggerPresent;
if (CheckRemoteDebuggerPresent(mAppProcessHandle, &debuggerPresent) && debuggerPresent)
return false;
if (SendMessageTimeoutA(mAppWindowHandle, WM_NULL, 0, 0, 0, 5000, nullptr) == 0)
return GetLastError() == ERROR_TIMEOUT;
}
return false;
}
void CrashMonitor::run()
{
try
@ -88,9 +148,24 @@ namespace Crash
signalApp();
bool running = true;
while (isAppAlive() && running)
bool frozen = false;
while (isAppAlive() && running && !mFreezeAbort)
{
if (waitApp())
if (isAppFrozen())
{
if (!frozen)
{
showFreezeMessageBox();
frozen = true;
}
}
else if (frozen)
{
hideFreezeMessageBox();
frozen = false;
}
if (!mFreezeAbort && waitApp())
{
shmLock();
@ -113,6 +188,16 @@ namespace Crash
}
}
if (frozen)
hideFreezeMessageBox();
if (mFreezeAbort)
{
TerminateProcess(mAppProcessHandle, 0xDEAD);
std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
}
}
catch (...)
{
@ -185,4 +270,43 @@ namespace Crash
}
}
void CrashMonitor::showFreezeMessageBox()
{
std::thread messageBoxThread([&]() {
SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" };
SDL_MessageBoxData messageBoxData = {
SDL_MESSAGEBOX_ERROR,
nullptr,
"OpenMW appears to have frozen",
"OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW hasn't actually frozen, this message box will disappear a within a few seconds of it becoming responsive.",
1,
&button,
nullptr
};
int buttonId;
if (SDL_ShowMessageBox(&messageBoxData, &buttonId) == 0 && buttonId == 0)
mFreezeAbort = true;
});
mFreezeMessageBoxThreadId = GetThreadId(messageBoxThread.native_handle());
messageBoxThread.detach();
}
void CrashMonitor::hideFreezeMessageBox()
{
if (!mFreezeMessageBoxThreadId)
return;
EnumWindows([](HWND handle, LPARAM param) -> BOOL {
CrashMonitor& crashMonitor = *(CrashMonitor*)param;
DWORD processId;
if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mFreezeMessageBoxThreadId && processId == GetCurrentProcessId())
PostMessage(handle, WM_CLOSE, 0, 0);
return true;
}, (LPARAM)this);
mFreezeMessageBoxThreadId = 0;
}
} // namespace Crash

View file

@ -1,7 +1,11 @@
#ifndef WINDOWS_CRASHMONITOR_HPP
#define WINDOWS_CRASHMONITOR_HPP
#include <windef.h>
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <atomic>
namespace Crash
{
@ -21,6 +25,8 @@ public:
private:
HANDLE mAppProcessHandle = nullptr;
DWORD mAppMainThreadId = 0;
HWND mAppWindowHandle = nullptr;
// triggered when the monitor process wants to wake the parent process (received via SHM)
HANDLE mSignalAppEvent = nullptr;
@ -31,17 +37,28 @@ private:
HANDLE mShmHandle = nullptr;
HANDLE mShmMutex = nullptr;
DWORD mFreezeMessageBoxThreadId = 0;
std::atomic_bool mFreezeAbort;
static std::unordered_map<HWINEVENTHOOK, CrashMonitor*> smEventHookOwners;
void signalApp() const;
bool waitApp() const;
bool isAppAlive() const;
bool isAppFrozen();
void shmLock();
void shmUnlock();
void handleCrash();
void showFreezeMessageBox();
void hideFreezeMessageBox();
};
} // namespace Crash

View file

@ -26,6 +26,7 @@ namespace Crash
struct Startup
{
HANDLE mAppProcessHandle;
DWORD mAppMainThreadId;
HANDLE mSignalApp;
HANDLE mSignalMonitor;
HANDLE mShmMutex;