summaryrefslogtreecommitdiffstats
path: root/dom/media/doctor/DecoderDoctorLogger.cpp
blob: 927650babc81da4c2511a72e4bbb4c4b0cc1e940 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "DecoderDoctorLogger.h"

#include "DDLogUtils.h"
#include "DDMediaLogs.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Unused.h"

namespace mozilla {

/* static */ Atomic<DecoderDoctorLogger::LogState, ReleaseAcquire>
    DecoderDoctorLogger::sLogState{DecoderDoctorLogger::scDisabled};

/* static */ const char* DecoderDoctorLogger::sShutdownReason = nullptr;

static DDMediaLogs* sMediaLogs;

/* static */
void DecoderDoctorLogger::Init() {
  MOZ_ASSERT(static_cast<LogState>(sLogState) == scDisabled);
  if (MOZ_LOG_TEST(sDecoderDoctorLoggerLog, LogLevel::Error) ||
      MOZ_LOG_TEST(sDecoderDoctorLoggerEndLog, LogLevel::Error)) {
    EnableLogging();
  }
}

// First DDLogShutdowner sets sLogState to scShutdown, to prevent further
// logging.
struct DDLogShutdowner {
  ~DDLogShutdowner() {
    DDL_INFO("Shutting down");
    // Prevent further logging, some may racily seep in, it's fine as the
    // logging infrastructure would still be alive until DDLogDeleter runs.
    DecoderDoctorLogger::ShutdownLogging();
  }
};
static UniquePtr<DDLogShutdowner> sDDLogShutdowner;

// Later DDLogDeleter will delete the message queue and media logs.
struct DDLogDeleter {
  ~DDLogDeleter() {
    if (sMediaLogs) {
      DDL_INFO("Final processing of collected logs");
      delete sMediaLogs;
      sMediaLogs = nullptr;
    }
  }
};
static UniquePtr<DDLogDeleter> sDDLogDeleter;

/* static */
void DecoderDoctorLogger::PanicInternal(const char* aReason, bool aDontBlock) {
  for (;;) {
    const LogState state = static_cast<LogState>(sLogState);
    if (state == scEnabling && !aDontBlock) {
      // Wait for the end of the enabling process (unless we're in it, in which
      // case we don't want to block.)
      continue;
    }
    if (state == scShutdown) {
      // Already shutdown, nothing more to do.
      break;
    }
    if (sLogState.compareExchange(state, scShutdown)) {
      // We are the one performing the first shutdown -> Record reason.
      sShutdownReason = aReason;
      // Free as much memory as possible.
      if (sMediaLogs) {
        // Shutdown the medialogs processing thread, and free as much memory
        // as possible.
        sMediaLogs->Panic();
      }
      // sMediaLogs and sQueue will be deleted by DDLogDeleter.
      // We don't want to delete them right now, because there could be a race
      // where another thread started logging or retrieving logs before we
      // changed the state to scShutdown, but has been delayed before actually
      // trying to write or read log messages, thereby causing a UAF.
    }
    // If someone else changed the state, we'll just loop around, and either
    // shutdown already happened elsewhere, or we'll try to shutdown again.
  }
}

/* static */
bool DecoderDoctorLogger::EnsureLogIsEnabled() {
#ifdef RELEASE_OR_BETA
  // Just refuse to enable DDLogger on release&beta because it makes it too easy
  // to trigger an OOM. See bug 1571648.
  return false;
#else
  for (;;) {
    LogState state = static_cast<LogState>(sLogState);
    switch (state) {
      case scDisabled:
        // Currently disabled, try to be the one to enable.
        if (sLogState.compareExchange(scDisabled, scEnabling)) {
          // We are the one to enable logging, state won't change (except for
          // possible shutdown.)
          // Create DDMediaLogs singleton, which will process the message queue.
          DDMediaLogs::ConstructionResult mediaLogsConstruction =
              DDMediaLogs::New();
          if (NS_FAILED(mediaLogsConstruction.mRv)) {
            PanicInternal("Failed to enable logging", /* aDontBlock */ true);
            return false;
          }
          MOZ_ASSERT(mediaLogsConstruction.mMediaLogs);
          sMediaLogs = mediaLogsConstruction.mMediaLogs;
          // Setup shutdown-time clean-up.
          MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(
              TaskCategory::Other,
              NS_NewRunnableFunction("DDLogger shutdown setup", [] {
                sDDLogShutdowner = MakeUnique<DDLogShutdowner>();
                ClearOnShutdown(&sDDLogShutdowner,
                                ShutdownPhase::XPCOMShutdown);
                sDDLogDeleter = MakeUnique<DDLogDeleter>();
                ClearOnShutdown(&sDDLogDeleter,
                                ShutdownPhase::XPCOMShutdownThreads);
              })));

          // Nobody else should change the state when *we* are enabling logging.
          MOZ_ASSERT(sLogState == scEnabling);
          sLogState = scEnabled;
          DDL_INFO("Logging enabled");
          return true;
        }
        // Someone else changed the state before our compareExchange, just loop
        // around to examine the new situation.
        break;
      case scEnabled:
        return true;
      case scEnabling:
        // Someone else is currently enabling logging, actively wait by just
        // looping, until the state changes.
        break;
      case scShutdown:
        // Shutdown is non-recoverable, we cannot enable logging again.
        return false;
    }
    // Not returned yet, loop around to examine the new situation.
  }
#endif
}

/* static */
void DecoderDoctorLogger::EnableLogging() { Unused << EnsureLogIsEnabled(); }

/* static */ RefPtr<DecoderDoctorLogger::LogMessagesPromise>
DecoderDoctorLogger::RetrieveMessages(
    const dom::HTMLMediaElement* aMediaElement) {
  if (MOZ_UNLIKELY(!EnsureLogIsEnabled())) {
    DDL_WARN("Request (for %p) but there are no logs", aMediaElement);
    return DecoderDoctorLogger::LogMessagesPromise::CreateAndReject(
        NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__);
  }
  return sMediaLogs->RetrieveMessages(aMediaElement);
}

/* static */
void DecoderDoctorLogger::Log(const char* aSubjectTypeName,
                              const void* aSubjectPointer,
                              DDLogCategory aCategory, const char* aLabel,
                              DDLogValue&& aValue) {
  if (IsDDLoggingEnabled()) {
    MOZ_ASSERT(sMediaLogs);
    sMediaLogs->Log(aSubjectTypeName, aSubjectPointer, aCategory, aLabel,
                    std::move(aValue));
  }
}

}  // namespace mozilla