Allow DriverCrashGuard to be used in content processes. (bug 1190281 part 7, r=mattwoodrow)

This commit is contained in:
David Anderson 2015-08-11 20:22:20 -07:00
parent 2dedaa3db6
commit 2cfc7d03e0
7 changed files with 249 additions and 93 deletions

View File

@ -2081,6 +2081,10 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
SEND_SHUTDOWN_MESSAGE));
}
cpm->RemoveContentProcess(this->ChildID());
if (mDriverCrashGuard) {
mDriverCrashGuard->NotifyCrashed();
}
}
void
@ -5178,6 +5182,39 @@ ContentParent::RecvGetGraphicsDeviceInitData(DeviceInitData* aOut)
return true;
}
bool
ContentParent::RecvBeginDriverCrashGuard(const uint32_t& aGuardType, bool* aOutCrashed)
{
// Only one driver crash guard should be active at a time, per-process.
MOZ_ASSERT(!mDriverCrashGuard);
UniquePtr<gfx::DriverCrashGuard> guard;
switch (gfx::CrashGuardType(aGuardType)) {
case gfx::CrashGuardType::D3D11Layers:
guard = MakeUnique<gfx::D3D11LayersCrashGuard>(this);
break;
default:
MOZ_ASSERT_UNREACHABLE("unknown crash guard type");
return false;
}
if (guard->Crashed()) {
*aOutCrashed = true;
return true;
}
*aOutCrashed = false;
mDriverCrashGuard = Move(guard);
return true;
}
bool
ContentParent::RecvEndDriverCrashGuard(const uint32_t& aGuardType)
{
mDriverCrashGuard = nullptr;
return true;
}
} // namespace dom
} // namespace mozilla

View File

@ -25,6 +25,7 @@
#include "nsIDOMGeoPositionCallback.h"
#include "nsIDOMGeoPositionErrorCallback.h"
#include "PermissionMessageUtils.h"
#include "DriverCrashGuard.h"
#define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
@ -814,6 +815,8 @@ private:
virtual bool RecvGetGraphicsFeatureStatus(const int32_t& aFeature,
int32_t* aStatus,
bool* aSuccess) override;
virtual bool RecvBeginDriverCrashGuard(const uint32_t& aGuardType, bool* aOutCrashed) override;
virtual bool RecvEndDriverCrashGuard(const uint32_t& aGuardType) override;
virtual bool RecvAddIdleObserver(const uint64_t& observerId,
const uint32_t& aIdleTimeInS) override;
@ -954,6 +957,8 @@ private:
nsRefPtr<mozilla::ProfileGatherer> mGatherer;
#endif
nsCString mProfile;
UniquePtr<gfx::DriverCrashGuard> mDriverCrashGuard;
};
} // namespace dom

View File

@ -933,6 +933,10 @@ parent:
sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, bool aSuccess);
// Driver crash guards. aGuardType must be a member of CrashGuardType.
sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected);
sync EndDriverCrashGuard(uint32_t aGuardType);
AddIdleObserver(uint64_t observerId, uint32_t idleTimeInS);
RemoveIdleObserver(uint64_t observerId, uint32_t idleTimeInS);

View File

@ -16,6 +16,7 @@
#include "mozilla/Telemetry.h"
#include "mozilla/Services.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/dom/ContentChild.h"
namespace mozilla {
namespace gfx {
@ -25,17 +26,17 @@ static const char* sCrashGuardNames[NUM_CRASH_GUARD_TYPES] = {
"d3d11layers",
};
DriverCrashGuard::DriverCrashGuard(CrashGuardType aType)
DriverCrashGuard::DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent)
: mType(aType)
, mMode(aContentParent ? Mode::Proxy : Mode::Normal)
, mInitialized(false)
, mGuardActivated(false)
, mCrashDetected(false)
{
MOZ_ASSERT(mType < CrashGuardType::NUM_TYPES);
mStatusPref.Assign("gfx.driver-init.status.");
mStatusPref.Assign("gfx.crash-guard.status.");
mStatusPref.Append(sCrashGuardNames[size_t(mType)]);
mGuardFilename.Assign(sCrashGuardNames[size_t(mType)]);
mGuardFilename.Append(".guard");
}
void
@ -52,120 +53,191 @@ DriverCrashGuard::InitializeIfNeeded()
void
DriverCrashGuard::Initialize()
{
if (!InitLockFilePath()) {
gfxCriticalError(CriticalLog::DefaultOptions(false)) << "Failed to create the graphics startup lockfile.";
if (XRE_IsContentProcess()) {
// Ask the parent whether or not activating the guard is okay. The parent
// won't bother if it detected a crash.
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected);
if (mCrashDetected) {
LogFeatureDisabled();
return;
}
ActivateGuard();
return;
}
if (RecoverFromDriverInitCrash()) {
// Always check whether or not the lock file exists. For example, we could
// have crashed creating a D3D9 device in the parent process, and on restart
// are now requesting one in the child process. We catch everything here.
if (RecoverFromCrash()) {
mCrashDetected = true;
return;
}
if (PrepareToGuard()) {
// Something in the environment changed, *or* a previous instance of this
// class already updated the environment in this session. Enable the
// guard.
AllowDriverInitAttempt();
// If the environment has changed, we always activate the guard. In the
// parent process this performs main-thread disk I/O. Child process guards
// only incur an IPC cost, so if we're proxying for a child process, we
// play it safe and activate the guard as long as we don't expect it to
// crash.
if (CheckOrRefreshEnvironment() ||
(mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed))
{
ActivateGuard();
return;
}
// If we got here and our status is "crashed", then the environment has not
// updated and we do not want to attempt to use the driver again.
if (GetStatus() == DriverInitStatus::Crashed) {
mCrashDetected = true;
LogFeatureDisabled();
}
}
DriverCrashGuard::~DriverCrashGuard()
{
if (mGuardFile) {
mGuardFile->Remove(false);
if (!mGuardActivated) {
return;
}
if (GetStatus() == DriverInitStatus::Attempting) {
// If we attempted to initialize the driver, and got this far without
// crashing, assume everything is okay.
SetStatus(DriverInitStatus::Okay);
if (XRE_IsParentProcess()) {
if (mGuardFile) {
mGuardFile->Remove(false);
}
// If during our initialization, no other process encountered a crash, we
// proceed to mark the status as okay.
if (GetStatus() != DriverInitStatus::Crashed) {
SetStatus(DriverInitStatus::Okay);
}
} else {
dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType));
}
#ifdef MOZ_CRASHREPORTER
// Remove the crash report annotation.
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
NS_LITERAL_CSTRING(""));
// Remove the crash report annotation.
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
NS_LITERAL_CSTRING(""));
#endif
}
}
bool
DriverCrashGuard::Crashed()
{
InitializeIfNeeded();
return gfxPrefs::DriverInitStatus() == int32_t(DriverInitStatus::Recovered);
// Note, we read mCrashDetected instead of GetStatus(), since in child
// processes we're not guaranteed that the prefs have been synced in
// time.
return mCrashDetected;
}
bool
DriverCrashGuard::InitLockFilePath()
nsCOMPtr<nsIFile>
DriverCrashGuard::GetGuardFile()
{
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(mGuardFile));
if (!mGuardFile) {
return false;
}
MOZ_ASSERT(XRE_IsParentProcess());
if (!NS_SUCCEEDED(mGuardFile->AppendNative(mGuardFilename))) {
return false;
nsCString filename;
filename.Assign(sCrashGuardNames[size_t(mType)]);
filename.Append(".guard");
nsCOMPtr<nsIFile> file;
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(file));
if (!file) {
return nullptr;
}
return true;
if (!NS_SUCCEEDED(file->AppendNative(filename))) {
return nullptr;
}
return file;
}
void
DriverCrashGuard::AllowDriverInitAttempt()
DriverCrashGuard::ActivateGuard()
{
// Create a temporary tombstone/lockfile.
FILE* fp;
if (!NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
MOZ_ASSERT(XRE_IsParentProcess());
mGuardActivated = true;
#ifdef MOZ_CRASHREPORTER
// Anotate crash reports only if we're a real guard. Otherwise, we could
// attribute a random parent process crash to a graphics problem in a child
// process.
if (mMode != Mode::Proxy) {
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
NS_LITERAL_CSTRING("1"));
}
#endif
// If we're in the content process, the rest of the guarding is handled
// in the parent.
if (XRE_IsContentProcess()) {
return;
}
fclose(fp);
SetStatus(DriverInitStatus::Attempting);
// Flush preferences, so if we crash, we don't think the environment has changed again.
FlushPreferences();
if (mMode != Mode::Proxy) {
// In parent process guards, we use two tombstones to detect crashes: a
// preferences and a zero-byte file on the filesystem.
FlushPreferences();
#ifdef MOZ_CRASHREPORTER
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
NS_LITERAL_CSTRING("1"));
#endif
// Create a temporary tombstone/lockfile.
FILE* fp = nullptr;
mGuardFile = GetGuardFile();
if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
return;
}
fclose(fp);
}
}
void
DriverCrashGuard::NotifyCrashed()
{
CheckOrRefreshEnvironment();
SetStatus(DriverInitStatus::Crashed);
FlushPreferences();
LogCrashRecovery();
}
bool
DriverCrashGuard::RecoverFromDriverInitCrash()
DriverCrashGuard::RecoverFromCrash()
{
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIFile> file = GetGuardFile();
bool exists;
if (mGuardFile &&
NS_SUCCEEDED(mGuardFile->Exists(&exists)) &&
exists)
if ((file &&
NS_SUCCEEDED(file->Exists(&exists)) &&
exists) ||
(GetStatus() == DriverInitStatus::Attempting))
{
// 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.
SetStatus(DriverInitStatus::Recovered);
UpdateBaseEnvironment();
UpdateEnvironment();
FlushPreferences();
LogCrashRecovery();
return true;
}
if (GetStatus() == DriverInitStatus::Recovered) {
// If we get here, we crashed in a previous session.
LogFeatureDisabled();
// until the environment changes.
if (file) {
file->Remove(false);
}
NotifyCrashed();
return true;
}
return false;
}
// Return true if the caller should proceed to guard for crashes. False if
// the environment has not changed.
// the environment has not changed. We persist the "changed" status across
// calls, so that after an environment changes, all guards for the new
// session are activated rather than just the first.
bool
DriverCrashGuard::PrepareToGuard()
DriverCrashGuard::CheckOrRefreshEnvironment()
{
// Our result can be cached statically since we don't check live prefs.
static bool sBaseInfoChanged = false;
static bool sBaseInfoChecked = false;
if (!sBaseInfoChecked) {
// None of the prefs we care about, so we cache the result statically.
sBaseInfoChecked = true;
sBaseInfoChanged = UpdateBaseEnvironment();
}
@ -173,7 +245,7 @@ DriverCrashGuard::PrepareToGuard()
// Always update the full environment, even if the base info didn't change.
return UpdateEnvironment() ||
sBaseInfoChanged ||
gfxPrefs::DriverInitStatus() == int32_t(DriverInitStatus::None);
GetStatus() == DriverInitStatus::Unknown;
}
bool
@ -186,16 +258,13 @@ DriverCrashGuard::UpdateBaseEnvironment()
// Driver properties.
mGfxInfo->GetAdapterDriverVersion(value);
changed |= CheckAndUpdatePref("gfx.driver-init.driverVersion", value);
changed |= CheckAndUpdatePref("driverVersion", value);
mGfxInfo->GetAdapterDeviceID(value);
changed |= CheckAndUpdatePref("gfx.driver-init.deviceID", value);
changed |= CheckAndUpdatePref("deviceID", value);
}
// 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 |= (GetStatus() == DriverInitStatus::None);
changed |= CheckAndUpdatePref("appVersion", NS_LITERAL_STRING(MOZ_APP_VERSION));
return changed;
}
@ -213,27 +282,40 @@ DriverCrashGuard::FeatureEnabled(int aFeature)
bool
DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue)
{
std::string pref = GetFullPrefName(aPrefName);
bool oldValue;
if (NS_SUCCEEDED(Preferences::GetBool(aPrefName, &oldValue)) &&
if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) &&
oldValue == aCurrentValue)
{
return false;
}
Preferences::SetBool(aPrefName, aCurrentValue);
Preferences::SetBool(pref.c_str(), aCurrentValue);
return true;
}
bool
DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue)
{
nsAdoptingString oldValue = Preferences::GetString(aPrefName);
std::string pref = GetFullPrefName(aPrefName);
nsAdoptingString oldValue = Preferences::GetString(pref.c_str());
if (oldValue == aCurrentValue) {
return false;
}
Preferences::SetString(aPrefName, aCurrentValue);
Preferences::SetString(pref.c_str(), aCurrentValue);
return true;
}
std::string
DriverCrashGuard::GetFullPrefName(const char* aPref)
{
return std::string("gfx.crash-guard.") +
std::string(sCrashGuardNames[uint32_t(mType)]) +
std::string(".") +
std::string(aPref);
}
DriverInitStatus
DriverCrashGuard::GetStatus() const
{
@ -243,19 +325,23 @@ DriverCrashGuard::GetStatus() const
void
DriverCrashGuard::SetStatus(DriverInitStatus aStatus)
{
MOZ_ASSERT(XRE_IsParentProcess());
Preferences::SetInt(mStatusPref.get(), int32_t(aStatus));
}
void
DriverCrashGuard::FlushPreferences()
{
MOZ_ASSERT(XRE_IsParentProcess());
if (nsIPrefService* prefService = Preferences::GetService()) {
prefService->SavePrefFile(nullptr);
}
}
D3D11LayersCrashGuard::D3D11LayersCrashGuard()
: DriverCrashGuard(CrashGuardType::D3D11Layers)
D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent)
: DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent)
{
}
@ -278,6 +364,7 @@ D3D11LayersCrashGuard::Initialize()
bool
D3D11LayersCrashGuard::UpdateEnvironment()
{
// Our result can be cached statically since we don't check live prefs.
static bool checked = false;
static bool changed = false;
@ -292,13 +379,13 @@ D3D11LayersCrashGuard::UpdateEnvironment()
#if defined(XP_WIN)
bool d2dEnabled = gfxPrefs::Direct2DForceEnabled() ||
(!gfxPrefs::Direct2DDisabled() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
changed |= CheckAndUpdateBoolPref("gfx.driver-init.feature-d2d", d2dEnabled);
changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
bool d3d11Enabled = !gfxPrefs::LayersPreferD3D9();
if (!FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS)) {
d3d11Enabled = false;
}
changed |= CheckAndUpdateBoolPref("gfx.driver-init.feature-d3d11", d3d11Enabled);
changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
#endif
}

View File

@ -10,14 +10,20 @@
#include "nsIGfxInfo.h"
#include "nsIFile.h"
#include "nsString.h"
#include <string>
namespace mozilla {
namespace dom {
class ContentParent;
} // namespace dom
namespace gfx {
enum class DriverInitStatus
{
// Drivers have not been initialized yet.
None,
Unknown,
// We're attempting to initialize drivers.
Attempting,
@ -26,13 +32,12 @@ enum class DriverInitStatus
Okay,
// We crashed during driver initialization, and have restarted.
Recovered
Crashed
};
enum class CrashGuardType : uint32_t
{
D3D11Layers,
NUM_TYPES
};
@ -48,10 +53,11 @@ enum class CrashGuardType : uint32_t
class DriverCrashGuard
{
public:
DriverCrashGuard(CrashGuardType aType);
~DriverCrashGuard();
DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent);
virtual ~DriverCrashGuard();
bool Crashed();
void NotifyCrashed();
// These are the values reported to Telemetry (GRAPHICS_DRIVER_STARTUP_TEST).
// Values should not change; add new values to the end.
@ -62,6 +68,14 @@ public:
FeatureDisabled = 3
};
enum class Mode {
// Normal operation.
Normal,
// Acting as a proxy between the parent and child process.
Proxy
};
protected:
virtual void Initialize();
virtual bool UpdateEnvironment() = 0;
@ -72,33 +86,39 @@ protected:
bool FeatureEnabled(int aFeature);
bool CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue);
bool CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue);
std::string GetFullPrefName(const char* aPref);
private:
// Either process.
void InitializeIfNeeded();
bool InitLockFilePath();
void AllowDriverInitAttempt();
bool RecoverFromDriverInitCrash();
void FlushPreferences();
bool PrepareToGuard();
bool CheckOrRefreshEnvironment();
bool UpdateBaseEnvironment();
DriverInitStatus GetStatus() const;
// Parent process only.
nsCOMPtr<nsIFile> GetGuardFile();
bool RecoverFromCrash();
void ActivateGuard();
void FlushPreferences();
void SetStatus(DriverInitStatus aStatus);
private:
CrashGuardType mType;
Mode mMode;
bool mInitialized;
bool mGuardActivated;
bool mCrashDetected;
nsCOMPtr<nsIFile> mGuardFile;
protected:
nsCString mStatusPref;
nsCString mGuardFilename;
nsCOMPtr<nsIGfxInfo> mGfxInfo;
};
class D3D11LayersCrashGuard final : public DriverCrashGuard
{
public:
D3D11LayersCrashGuard();
explicit D3D11LayersCrashGuard(dom::ContentParent* aContentParent = nullptr);
protected:
void Initialize() override;

View File

@ -77,6 +77,12 @@ SOURCES += [
FAIL_ON_WARNINGS = True
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/dom/ipc', # for ContentChild.h
]
FINAL_LIBRARY = 'xul'
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']

View File

@ -232,9 +232,6 @@ private:
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(Once, "gfx.font_rendering.directwrite.enabled", DirectWriteFontRenderingEnabled, bool, false);
DECL_GFX_PREF(Live, "gfx.gralloc.fence-with-readpixels", GrallocFenceWithReadPixels, bool, false);
DECL_GFX_PREF(Live, "gfx.layerscope.enabled", LayerScopeEnabled, bool, false);