diff options
author | Michael Meeks <michael.meeks@collabora.com> | 2015-08-25 11:44:41 +0100 |
---|---|---|
committer | Michael Meeks <michael.meeks@collabora.com> | 2015-08-26 07:25:39 +0000 |
commit | e207dffe24aaec4d446baca67ffbbd6f67f52484 (patch) | |
tree | 5689ac5620474c139c8bff92704729752614221e | |
parent | f0a149fe1c3224cef17f15e574b0a818923b57f6 (diff) |
tdf#93614 - detect hanging OpenGL drivers with a watchdog.
If an OpenGL zone takes >2s to make progress, disable OpenGL.
If an OpenGL zone takes >5s to make progress, abort the app.
Reviewed-on: https://gerrit.libreoffice.org/17986
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
Tested-by: Michael Meeks <michael.meeks@collabora.com>
Conflicts:
vcl/workben/vcldemo.cxx
Change-Id: I776c06a3f8ba460ff9842a9130c21f9ee2147eee
Reviewed-on: https://gerrit.libreoffice.org/17995
Reviewed-by: Markus Mohrhard <markus.mohrhard@googlemail.com>
Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
Tested-by: Michael Meeks <michael.meeks@collabora.com>
-rw-r--r-- | vcl/inc/opengl/watchdog.hxx | 31 | ||||
-rw-r--r-- | vcl/inc/opengl/zone.hxx | 31 | ||||
-rw-r--r-- | vcl/source/app/svmain.cxx | 4 | ||||
-rw-r--r-- | vcl/source/opengl/OpenGLHelper.cxx | 130 | ||||
-rw-r--r-- | vcl/workben/vcldemo.cxx | 69 |
5 files changed, 239 insertions, 26 deletions
diff --git a/vcl/inc/opengl/watchdog.hxx b/vcl/inc/opengl/watchdog.hxx new file mode 100644 index 000000000000..b88c46831c36 --- /dev/null +++ b/vcl/inc/opengl/watchdog.hxx @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_VCL_INC_OPENGL_WATCHDOG_H +#define INCLUDED_VCL_INC_OPENGL_WATCHDOG_H + +#include <sal/config.h> +#include <sal/types.h> +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> + +class OpenGLWatchdogThread : private salhelper::Thread +{ + OpenGLWatchdogThread(); + virtual void execute() SAL_OVERRIDE; +public: + using salhelper::Thread::acquire; + using salhelper::Thread::release; + static void start(); + static void stop(); +}; + +#endif // INCLUDED_VCL_INC_OPENGL_WATCHDOG_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/opengl/zone.hxx b/vcl/inc/opengl/zone.hxx index c251c4fa85d8..44265c526693 100644 --- a/vcl/inc/opengl/zone.hxx +++ b/vcl/inc/opengl/zone.hxx @@ -7,28 +7,39 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef INCLUDED_VCL_INC_OPENGL_GUARD_H -#define INCLUDED_VCL_INC_OPENGL_GUARD_H +#ifndef INCLUDED_VCL_INC_OPENGL_ZONE_H +#define INCLUDED_VCL_INC_OPENGL_ZONE_H #include <sal/config.h> +#include <sal/types.h> +#include <vcl/dllapi.h> + +class OpenGLZoneTest; +class OpenGLWatchdogThread; /** * We want to be able to detect if a given crash came * from the OpenGL code, so use this helper to track that. */ -class OpenGLSalGraphicsImpl; class OpenGLZone { - static int gnInOpenGLZone; + friend class OpenGLZoneTest; + friend class OpenGLWatchdogThread; friend class OpenGLSalGraphicsImpl; - static void enter() { gnInOpenGLZone++; } - static void leave() { gnInOpenGLZone--; } + + /// how many times have we entered a GL zone + static volatile sal_uInt64 gnEnterCount; + /// how many times have we left a new GL zone + static volatile sal_uInt64 gnLeaveCount; + + static VCL_DLLPUBLIC void enter(); + static VCL_DLLPUBLIC void leave(); public: - OpenGLZone() { enter(); } - ~OpenGLZone() { leave(); } - static bool isInZone() { return gnInOpenGLZone > 0; } + OpenGLZone() { gnEnterCount++; } + ~OpenGLZone() { gnLeaveCount++; } + static bool isInZone() { return gnEnterCount != gnLeaveCount; } static void hardDisable(); }; -#endif // INCLUDED_VCL_INC_OPENGL_PROGRAM_H +#endif // INCLUDED_VCL_INC_OPENGL_ZONE_H /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx index 4c3ef9a6267c..b24714da147c 100644 --- a/vcl/source/app/svmain.cxx +++ b/vcl/source/app/svmain.cxx @@ -82,6 +82,7 @@ #include "uno/current_context.hxx" #include "opengl/zone.hxx" +#include "opengl/watchdog.hxx" #if OSL_DEBUG_LEVEL > 0 #include <typeinfo> @@ -189,7 +190,10 @@ int ImplSVMain() pSVData->mxAccessBridge.clear(); } + OpenGLWatchdogThread::stop(); + DeInitVCL(); + return nReturn; } diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx index 967d4c5259b1..daa2af01e169 100644 --- a/vcl/source/opengl/OpenGLHelper.cxx +++ b/vcl/source/opengl/OpenGLHelper.cxx @@ -26,6 +26,8 @@ #include <vector> #include "opengl/zone.hxx" +#include "opengl/watchdog.hxx" +#include <osl/conditn.h> #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID #include "opengl/x11/X11DeviceInfo.hxx" @@ -446,11 +448,101 @@ bool OpenGLHelper::supportsVCLOpenGL() return true; } -/// How many nested OpenGL code-paths are we inside ? -int OpenGLZone::gnInOpenGLZone = 0; +sal_uInt64 volatile OpenGLZone::gnEnterCount = 0; +sal_uInt64 volatile OpenGLZone::gnLeaveCount = 0; + +void OpenGLZone::enter() { gnEnterCount++; } +void OpenGLZone::leave() { gnLeaveCount++; } + +namespace { + static volatile bool gbWatchdogFiring = false; + static oslCondition gpWatchdogExit = NULL; + static rtl::Reference<OpenGLWatchdogThread> gxWatchdog; +} + +OpenGLWatchdogThread::OpenGLWatchdogThread() + : salhelper::Thread("OpenGL Watchdog") +{ +} + +void OpenGLWatchdogThread::execute() +{ + static const int nDisableEntries = 4; // 2 seconds - disable GL + static const int nAbortAfter = 10; // 5 seconds - not coming back; abort + int nUnchanged = 0; // how many unchanged nEnters + + TimeValue aHalfSecond; + aHalfSecond.Seconds = 0; + aHalfSecond.Nanosec = 1000*1000*1000/2; + + do { + sal_uInt64 nLastEnters = OpenGLZone::gnEnterCount; + + osl_waitCondition(gpWatchdogExit, &aHalfSecond); + + if (OpenGLZone::isInZone()) + { + if (nLastEnters == OpenGLZone::gnEnterCount) + nUnchanged++; + else + nUnchanged = 0; + SAL_INFO("vcl.opengl", "GL watchdog - unchanged " << + nUnchanged << " enter count " << + OpenGLZone::gnEnterCount); + + // Not making progress + if (nUnchanged == nDisableEntries) + { + gbWatchdogFiring = true; + SAL_WARN("vcl.opengl", "Watchdog triggered: hard disable GL"); + OpenGLZone::hardDisable(); + gbWatchdogFiring = false; + } + + if (nUnchanged == nAbortAfter) + { + SAL_WARN("vcl.opengl", "Watchdog gave up: aborting"); + gbWatchdogFiring = true; + std::abort(); + } + } + else + { + nUnchanged = 0; + } + } while (!osl_checkCondition(gpWatchdogExit)); +} + +void OpenGLWatchdogThread::start() +{ + assert (gxWatchdog == NULL); + gpWatchdogExit = osl_createCondition(); + gxWatchdog = rtl::Reference<OpenGLWatchdogThread>(new OpenGLWatchdogThread()); + gxWatchdog->launch(); +} + +void OpenGLWatchdogThread::stop() +{ + if (gbWatchdogFiring) + return; // in watchdog thread + + if (gpWatchdogExit) + osl_setCondition(gpWatchdogExit); + + if (gxWatchdog.is()) + { + gxWatchdog->join(); + gxWatchdog.clear(); + } + + if (gpWatchdogExit) + osl_destroyCondition(gpWatchdogExit); + gpWatchdogExit = NULL; +} /** - * Called from a signal handler if we get a crash in some GL code + * Called from a signal handler or watchdog thread if we get + * a crash or hang in some GL code. */ void OpenGLZone::hardDisable() { @@ -471,6 +563,8 @@ void OpenGLZone::hardDisable() css::configuration::theDefaultProvider::get( comphelper::getProcessComponentContext()), css::uno::UNO_QUERY_THROW)->flush(); + + OpenGLWatchdogThread::stop(); } } @@ -502,25 +596,31 @@ bool OpenGLHelper::isVCLOpenGLEnabled() bSet = true; bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get(); + + bool bRet = false; if (bForceOpenGL) - return true; + bRet = true; - if (!supportsVCLOpenGL()) + else if (!supportsVCLOpenGL()) + bRet = false; + else { - return false; - } + static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL"); - static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL"); + bEnable = bEnableGLEnv; - bEnable = bEnableGLEnv; + static bool bDuringBuild = getenv("VCL_HIDE_WINDOWS"); + if (bDuringBuild && !bEnable /* env. enable overrides */) + bEnable = false; + else if (officecfg::Office::Common::VCL::UseOpenGL::get()) + bEnable = true; - static bool bDuringBuild = getenv("VCL_HIDE_WINDOWS"); - if (bDuringBuild && !bEnable /* env. enable overrides */) - bEnable = false; - else if (officecfg::Office::Common::VCL::UseOpenGL::get()) - bEnable = true; + bRet = bEnable; + } + if (bRet) + OpenGLWatchdogThread::start(); - return bEnable; + return bRet; } #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined(LIBO_HEADLESS) diff --git a/vcl/workben/vcldemo.cxx b/vcl/workben/vcldemo.cxx index a80409a3cbcd..e0218e0e10af 100644 --- a/vcl/workben/vcldemo.cxx +++ b/vcl/workben/vcldemo.cxx @@ -41,6 +41,7 @@ #include <basegfx/numeric/ftools.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> #include <vcldemo-debug.hxx> +#include <opengl/zone.hxx> #include <rtl/math.hxx> @@ -1457,13 +1458,23 @@ class DemoWidgets : public WorkWindow VclPtr<VclBox> mpBox; VclPtr<ToolBox> mpToolbox; VclPtr<PushButton> mpButton; + VclPtr<VclHBox> mpHBox; + VclPtr<CheckBox> mpGLCheck; + VclPtr<ComboBox> mpGLCombo; + VclPtr<PushButton> mpGLButton; + + DECL_LINK(GLTestClick, void *); public: DemoWidgets() : WorkWindow(NULL, WB_STDWORK), mpBox(VclPtrInstance<VclVBox>(this, false, 3)), mpToolbox(VclPtrInstance<ToolBox>(mpBox.get())), - mpButton(VclPtrInstance<PushButton>(mpBox.get())) + mpButton(VclPtrInstance<PushButton>(mpBox.get())), + mpHBox(VclPtrInstance<VclHBox>(mpBox.get(), true, 3)), + mpGLCheck(VclPtrInstance<CheckBox>(mpHBox.get())), + mpGLCombo(VclPtrInstance<ComboBox>(mpHBox.get())), + mpGLButton(VclPtrInstance<PushButton>(mpHBox.get())) { SetText("VCL widget demo"); @@ -1484,11 +1495,27 @@ public: mpButton->SetText("Click me; go on"); mpButton->Show(); + mpGLCheck->SetText("Test in OGL zone"); + mpGLCheck->Show(); + mpGLCombo->InsertEntry("sleep 1 second"); + mpGLCombo->InsertEntry("sleep 3 seconds"); + mpGLCombo->InsertEntry("sleep 7 seconds"); + mpGLCombo->SelectEntryPos(2); + mpGLCombo->Show(); + mpGLButton->SetText("Execute test"); + mpGLButton->SetClickHdl(LINK(this,DemoWidgets,GLTestClick)); + mpGLButton->Show(); + mpHBox->Show(); + Show(); } virtual ~DemoWidgets() { disposeOnce(); } virtual void dispose() SAL_OVERRIDE { + mpGLButton.disposeAndClear(); + mpGLCombo.disposeAndClear(); + mpGLCheck.disposeAndClear(); + mpHBox.disposeAndClear(); mpBox.disposeAndClear(); mpToolbox.disposeAndClear(); mpButton.disposeAndClear(); @@ -1521,6 +1548,46 @@ public: } }; +class OpenGLZoneTest { +public: + static void enter() { OpenGLZone::enter(); } + static void leave() { OpenGLZone::leave(); } +}; + +IMPL_LINK_NOARG(DemoWidgets,GLTestClick) +{ + sal_Int32 nSelected = mpGLCombo->GetSelectEntryPos(); + + TimeValue aDelay; + aDelay.Seconds = 0; + aDelay.Nanosec = 0; + switch (nSelected) + { + case 0: + aDelay.Seconds = 1; + break; + case 1: + aDelay.Seconds = 3; + break; + case 2: + aDelay.Seconds = 7; + break; + default: + break; + } + + bool bEnterLeave = mpGLCheck->IsChecked(); + if (bEnterLeave) + OpenGLZoneTest::enter(); + + osl_waitThread(&aDelay); + + if (bEnterLeave) + OpenGLZoneTest::leave(); + + return 0; +} + class DemoPopup : public FloatingWindow { public: |