Disable layers acceleration on Windows if the last startup crashed during driver initialization. (bug 1168935 part 1, r=mattwoodrow)
☠☠ backed out by 7a68ee30a574 ☠ ☠
authorDavid Anderson <danderson@mozilla.com>
Wed, 10 Jun 2015 21:59:03 -0700
changeset 248243 606cb8f0882590c86e09a2186eeb41c9a1c6da43
parent 248242 2f26ece1e25f6ca5f9eecc5dda194e6188434321
child 248244 45cdca10d5cd53afc01d4ce33cca6639417219d2
push id28893
push userkwierso@gmail.com
push dateFri, 12 Jun 2015 00:02:58 +0000
treeherderautoland@8cf9d3e497f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1168935
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Disable layers acceleration on Windows if the last startup crashed during driver initialization. (bug 1168935 part 1, r=mattwoodrow)
gfx/src/DriverInitCrashDetection.cpp
gfx/src/DriverInitCrashDetection.h
gfx/src/moz.build
gfx/thebes/gfxPrefs.h
gfx/thebes/gfxWindowsPlatform.cpp
new file mode 100644
--- /dev/null
+++ b/gfx/src/DriverInitCrashDetection.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "DriverInitCrashDetection.h"
+#include "gfxPrefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/Logging.h"
+
+namespace mozilla {
+namespace gfx {
+
+bool DriverInitCrashDetection::sDisableAcceleration = false;
+bool DriverInitCrashDetection::sEnvironmentHasBeenUpdated = false;
+
+DriverInitCrashDetection::DriverInitCrashDetection()
+ : mIsChromeProcess(XRE_GetProcessType() == GeckoProcessType_Default)
+{
+  // Only use the lockfile in the privileged process, which is responsible for
+  // the first driver initialization run. Child processes can't access the
+  // filesystme anyway.
+  if (mIsChromeProcess && !InitLockFilePath()) {
+    return;
+  }
+
+  if (RecoverFromDriverInitCrash()) {
+    if (!sDisableAcceleration) {
+      // This is the first time we're checking for a crash recovery, so print
+      // a message and disable acceleration for anyone who asks for it.
+      gfxCriticalError(CriticalLog::DefaultOptions(false)) << "Recovered from graphics driver startup crash; acceleration disabled.";
+      sDisableAcceleration = true;
+    }
+    return;
+  }
+
+  // If we previously disabled acceleration, we should have gone through
+  // RecoverFromDriverInitCrash().
+  MOZ_ASSERT(!sDisableAcceleration);
+
+  if (mIsChromeProcess &&
+      (UpdateEnvironment() || sEnvironmentHasBeenUpdated))
+  {
+    // Something in the environment changed, *or* a previous instance of this
+    // class already updated the environment. Allow a fresh attempt at driver
+    // acceleration. This doesn't mean the previous attempt failed, it just
+    // means we want to detect whether the new environment crashes.
+    AllowDriverInitAttempt();
+    sEnvironmentHasBeenUpdated = true;
+    return;
+  }
+}
+
+DriverInitCrashDetection::~DriverInitCrashDetection()
+{
+  if (mLockFile) {
+    mLockFile->Remove(false);
+  }
+
+  if (gfxPrefs::DriverInitStatus() == int32_t(DriverInitStatus::Attempting)) {
+    // If we attempted to initialize the driver, and got this far without
+    // crashing, assume everything is okay.
+    gfxPrefs::SetDriverInitStatus(int32_t(DriverInitStatus::Okay));
+    gfxCriticalError(CriticalLog::DefaultOptions(false)) << "Successfully verified new graphics environment.";
+  }
+}
+
+bool
+DriverInitCrashDetection::InitLockFilePath()
+{
+  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(mLockFile));
+  if (!mLockFile) {
+    return false;
+  }
+  if (!NS_SUCCEEDED(mLockFile->AppendNative(NS_LITERAL_CSTRING("gfxinit.lock")))) {
+    return false;
+  }
+  return true;
+}
+
+void
+DriverInitCrashDetection::AllowDriverInitAttempt()
+{
+  // Create a temporary tombstone/lockfile.
+  FILE* fp;
+  if (!NS_SUCCEEDED(mLockFile->OpenANSIFileDesc("w", &fp))) {
+    return;
+  }
+  fclose(fp);
+
+  gfxPrefs::SetDriverInitStatus(int32_t(DriverInitStatus::Attempting));
+
+  // Flush preferences, so if we crash, we don't think the environment has changed again.
+  FlushPreferences();
+}
+
+bool
+DriverInitCrashDetection::RecoverFromDriverInitCrash()
+{
+  bool exists;
+  if (mLockFile &&
+      NS_SUCCEEDED(mLockFile->Exists(&exists)) &&
+      exists)
+  {
+    // If we get here, we've just recovered from a crash. Disable acceleration
+    // until the environment changes. Since we may have crashed before
+    // preferences we're flushed, we cache the environment again, then flush
+    // preferences so child processes can start right away.
+    gfxPrefs::SetDriverInitStatus(int32_t(DriverInitStatus::Recovered));
+    UpdateEnvironment();
+    FlushPreferences();
+    return true;
+  }
+  if (gfxPrefs::DriverInitStatus() == int32_t(DriverInitStatus::Recovered)) {
+    // If we get here, we crashed in the current environment and have already
+    // disabled acceleration.
+    return true;
+  }
+  return false;
+}
+
+bool
+DriverInitCrashDetection::UpdateEnvironment()
+{
+  mGfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+
+  bool changed = false;
+  if (mGfxInfo) {
+    nsString value;
+
+    // Driver properties.
+    mGfxInfo->GetAdapterDriverVersion(value);
+    changed |= CheckAndUpdatePref("gfx.driver-init.driverVersion", value);
+    mGfxInfo->GetAdapterDeviceID(value);
+    changed |= CheckAndUpdatePref("gfx.driver-init.deviceID", value);
+
+    // Feature status.
+#if defined(XP_WIN)
+    bool d2dEnabled = gfxPrefs::Direct2DForceEnabled() ||
+                      (!gfxPrefs::Direct2DDisabled() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
+    changed |= CheckAndUpdateBoolPref("gfx.driver-init.feature-d2d", d2dEnabled);
+
+    bool d3d11Enabled = !gfxPrefs::LayersPreferD3D9();
+    if (!FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS)) {
+      d3d11Enabled = false;
+    }
+    changed |= CheckAndUpdateBoolPref("gfx.driver-init.feature-d3d11", d3d11Enabled);
+#endif
+  }
+
+  // Firefox properties.
+  changed |= CheckAndUpdatePref("gfx.driver-init.appVersion", NS_LITERAL_STRING(MOZ_APP_VERSION));
+
+  // Finally, mark as changed if the status has been reset by the user.
+  changed |= (gfxPrefs::DriverInitStatus() == int32_t(DriverInitStatus::None));
+
+  mGfxInfo = nullptr;
+  return changed;
+}
+
+bool
+DriverInitCrashDetection::FeatureEnabled(int aFeature)
+{
+  int32_t status;
+  if (!NS_SUCCEEDED(mGfxInfo->GetFeatureStatus(aFeature, &status))) {
+    return false;
+  }
+  return status == nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+bool
+DriverInitCrashDetection::CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue)
+{
+  bool oldValue;
+  if (NS_SUCCEEDED(Preferences::GetBool(aPrefName, &oldValue)) &&
+      oldValue == aCurrentValue)
+  {
+    return false;
+  }
+  Preferences::SetBool(aPrefName, aCurrentValue);
+  return true;
+}
+
+bool
+DriverInitCrashDetection::CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue)
+{
+  nsAdoptingString oldValue = Preferences::GetString(aPrefName);
+  if (oldValue == aCurrentValue) {
+    return false;
+  }
+  Preferences::SetString(aPrefName, aCurrentValue);
+  return true;
+}
+
+void
+DriverInitCrashDetection::FlushPreferences()
+{
+  if (nsIPrefService* prefService = Preferences::GetService()) {
+    prefService->SavePrefFile(nullptr);
+  }
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/src/DriverInitCrashDetection.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 gfx_src_DriverInitCrashDetection_h__
+#define gfx_src_DriverInitCrashDetection_h__
+
+#include "gfxCore.h"
+#include "nsCOMPtr.h"
+#include "nsIGfxInfo.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace gfx {
+
+enum class DriverInitStatus
+{
+  // Drivers have not been initialized yet.
+  None,
+
+  // We're attempting to initialize drivers.
+  Attempting,
+
+  // Drivers were successfully initialized last run.
+  Okay,
+
+  // We crashed during driver initialization, and have restarted.
+  Recovered
+};
+
+class DriverInitCrashDetection
+{
+public:
+  DriverInitCrashDetection();
+  ~DriverInitCrashDetection();
+
+  bool DisableAcceleration() const {
+    return sDisableAcceleration;
+  }
+
+private:
+  bool InitLockFilePath();
+  bool UpdateEnvironment();
+  bool CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue);
+  bool CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue);
+  bool FeatureEnabled(int aFeature);
+  void AllowDriverInitAttempt();
+  bool RecoverFromDriverInitCrash();
+  void FlushPreferences();
+
+private:
+  static bool sDisableAcceleration;
+  static bool sEnvironmentHasBeenUpdated;
+
+private:
+  bool mIsChromeProcess;
+  nsCOMPtr<nsIGfxInfo> mGfxInfo;
+  nsCOMPtr<nsIFile> mLockFile;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfx_src_DriverInitCrashDetection_h__
+
--- a/gfx/src/moz.build
+++ b/gfx/src/moz.build
@@ -6,17 +6,20 @@
 
 XPIDL_SOURCES += [
     'nsIFontEnumerator.idl',
     'nsIScriptableRegion.idl',
 ]
 
 XPIDL_MODULE = 'gfx'
 
+DEFINES['MOZ_APP_VERSION'] = '"%s"' % CONFIG['MOZ_APP_VERSION']
+
 EXPORTS += [
+    'DriverInitCrashDetection.h',
     'FilterSupport.h',
     'gfxCore.h',
     'gfxCrashReporterUtils.h',
     'nsBoundingMetrics.h',
     'nsColor.h',
     'nsColorNameList.h',
     'nsColorNames.h',
     'nsCoord.h',
@@ -45,16 +48,17 @@ EXPORTS.mozilla.gfx += [
 
 if CONFIG['MOZ_X11']:
     EXPORTS.mozilla += ['X11Util.h']
     SOURCES += [
         'X11Util.cpp',
     ]
 
 UNIFIED_SOURCES += [
+    'DriverInitCrashDetection.cpp',
     'FilterSupport.cpp',
     'gfxCrashReporterUtils.cpp',
     'nsColor.cpp',
     'nsFont.cpp',
     'nsFontMetrics.cpp',
     'nsRect.cpp',
     'nsRegion.cpp',
     'nsRenderingContext.cpp',
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -221,16 +221,19 @@ private:
   DECL_GFX_PREF(Live, "gfx.color_management.mode",             CMSMode, int32_t,-1);
   // The zero default here should match QCMS_INTENT_DEFAULT from qcms.h
   DECL_GFX_PREF(Live, "gfx.color_management.rendering_intent", CMSRenderingIntent, int32_t, 0);
 
   DECL_GFX_PREF(Once, "gfx.direct2d.disabled",                 Direct2DDisabled, bool, false);
   DECL_GFX_PREF(Once, "gfx.direct2d.force-enabled",            Direct2DForceEnabled, bool, false);
   DECL_GFX_PREF(Live, "gfx.direct2d.use1_1",                   Direct2DUse1_1, bool, false);
   DECL_GFX_PREF(Live, "gfx.draw-color-bars",                   CompositorDrawColorBars, bool, false);
+  // This should be set to values in the DriverInitStatus enumeration found in
+  // DriverInitCrashDetection.h.
+  DECL_GFX_PREF(Live, "gfx.driver-init.status",                DriverInitStatus, int32_t, 0);
   DECL_GFX_PREF(Live, "gfx.gralloc.fence-with-readpixels",     GrallocFenceWithReadPixels, bool, false);
   DECL_GFX_PREF(Live, "gfx.layerscope.enabled",                LayerScopeEnabled, bool, false);
   DECL_GFX_PREF(Live, "gfx.layerscope.port",                   LayerScopePort, int32_t, 23456);
   // Note that        "gfx.logging.level" is defined in Logging.h
   DECL_GFX_PREF(Once, "gfx.logging.crash.length",              GfxLoggingCrashLength, uint32_t, 6);
   DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled",             PerfWarnings, bool, false);
   DECL_GFX_PREF(Once, "gfx.touch.resample",                    TouchResampling, bool, false);
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -73,16 +73,17 @@
 #include "SurfaceCache.h"
 #include "gfxPrefs.h"
 
 #if defined(MOZ_CRASHREPORTER)
 #include "nsExceptionHandler.h"
 #endif
 
 #include "VsyncSource.h"
+#include "DriverInitCrashDetection.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 using namespace mozilla::image;
 
 DCFromDrawTarget::DCFromDrawTarget(DrawTarget& aDrawTarget)
@@ -647,16 +648,21 @@ gfxWindowsPlatform::CreateDevice(nsRefPt
   return device ? S_OK : hr;
 }
 #endif
 
 void
 gfxWindowsPlatform::VerifyD2DDevice(bool aAttemptForce)
 {
 #ifdef CAIRO_HAS_D2D_SURFACE
+    DriverInitCrashDetection detectCrashes;
+    if (detectCrashes.DisableAcceleration()) {
+      return;
+    }
+
     if (mD2DDevice) {
         ID3D10Device1 *device = cairo_d2d_device_get_device(mD2DDevice);
 
         if (SUCCEEDED(device->GetDeviceRemovedReason())) {
             return;
         }
         mD2DDevice = nullptr;
 
@@ -1859,17 +1865,18 @@ gfxWindowsPlatform::InitD3D11Devices()
   // blacklisted, then this function will abort if WARP is disabled, causing us
   // to fallback to D3D9 or Basic layers. If WARP is not disabled it will use
   // a WARP device which should always be available on Windows 7 and higher.
 
   mD3D11DeviceInitialized = true;
 
   MOZ_ASSERT(!mD3D11Device); 
 
-  if (InSafeMode()) {
+  DriverInitCrashDetection detectCrashes;
+  if (InSafeMode() || detectCrashes.DisableAcceleration()) {
     return;
   }
 
   bool useWARP = false;
 
   nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
   if (gfxInfo) {
     int32_t status;