blob: 327106924565d2cd72b1b534e246952aeb3422ad [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/webcodecs/reclaimable_codec.h"
#include "base/location.h"
#include "base/time/default_tick_clock.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/quota_exceeded_error.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_pressure_manager.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_pressure_manager_provider.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
constexpr base::TimeDelta ReclaimableCodec::kInactivityReclamationThreshold;
ReclaimableCodec::ReclaimableCodec(CodecType type, ExecutionContext* context)
: ExecutionContextLifecycleObserver(context),
codec_type_(type),
tick_clock_(base::DefaultTickClock::GetInstance()),
inactivity_threshold_(kInactivityReclamationThreshold),
last_activity_(tick_clock_->NowTicks()),
activity_timer_(context->GetTaskRunner(TaskType::kInternalMedia),
this,
&ReclaimableCodec::OnActivityTimerFired) {
DCHECK(context);
// Do this last, it will immediately re-enter via OnLifecycleStateChanged().
observer_handle_ = context->GetScheduler()->AddLifecycleObserver(
FrameOrWorkerScheduler::ObserverType::kWorkerScheduler,
BindRepeating(&ReclaimableCodec::OnLifecycleStateChanged,
WrapWeakPersistent(this)));
}
void ReclaimableCodec::Trace(Visitor* visitor) const {
visitor->Trace(activity_timer_);
ExecutionContextLifecycleObserver::Trace(visitor);
}
void ReclaimableCodec::ApplyCodecPressure() {
if (is_applying_pressure_)
return;
is_applying_pressure_ = true;
if (auto* pressure_manager = PressureManager())
pressure_manager->AddCodec(this);
}
void ReclaimableCodec::ReleaseCodecPressure() {
if (!is_applying_pressure_) {
DCHECK(!activity_timer_.IsActive());
return;
}
if (auto* pressure_manager = PressureManager()) {
// If we fail to get |pressure_manager| here (say, because the
// ExecutionContext is being destroyed), this is harmless. The
// CodecPressureManager maintains its own local pressure count, and it will
// properly decrement it from the global pressure count upon the manager's
// disposal. The CodecPressureManager's WeakMember reference to |this| will
// be cleared by the GC when |this| is disposed. The manager might still
// call into SetGlobalPressureExceededFlag() before |this| is disposed, but
// we will simply noop those calls.
pressure_manager->RemoveCodec(this);
}
// We might still exceed global codec pressure at this point, but this codec
// isn't contributing to it, and needs to reset its own flag.
SetGlobalPressureExceededFlag(false);
is_applying_pressure_ = false;
}
void ReclaimableCodec::Dispose() {
if (!is_applying_pressure_)
return;
if (auto* pressure_manager = PressureManager())
pressure_manager->OnCodecDisposed(this);
}
void ReclaimableCodec::SetGlobalPressureExceededFlag(
bool global_pressure_exceeded) {
if (!is_applying_pressure_) {
// We should only hit this call because we failed to get the
// PressureManager() in ReleaseCodecPressure(). See the note above.
DCHECK(!PressureManager());
return;
}
if (global_pressure_exceeded_ == global_pressure_exceeded)
return;
global_pressure_exceeded_ = global_pressure_exceeded;
OnReclamationPreconditionsUpdated();
}
void ReclaimableCodec::OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState lifecycle_state) {
DVLOG(5) << __func__
<< " lifecycle_state=" << static_cast<int>(lifecycle_state);
bool is_backgrounded =
lifecycle_state != scheduler::SchedulingLifecycleState::kNotThrottled;
// Several life cycle states map to "backgrounded", but we only want to
// observe the transition.
if (is_backgrounded == is_backgrounded_)
return;
is_backgrounded_ = is_backgrounded;
// Make sure we wait the full inactivity timer period before reclaiming a
// newly backgrounded codec.
if (is_backgrounded_)
MarkCodecActive();
OnReclamationPreconditionsUpdated();
}
void ReclaimableCodec::SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState state) {
OnLifecycleStateChanged(state);
}
void ReclaimableCodec::SimulateCodecReclaimedForTesting() {
auto* message = "Codec reclaimed for testing.";
if (RuntimeEnabledFeatures::QuotaExceededErrorUpdateEnabled()) {
OnCodecReclaimed(MakeGarbageCollected<QuotaExceededError>(message));
} else {
OnCodecReclaimed(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kQuotaExceededError, message));
}
}
void ReclaimableCodec::SimulateActivityTimerFiredForTesting() {
OnActivityTimerFired(nullptr);
}
void ReclaimableCodec::MarkCodecActive() {
last_activity_ = tick_clock_->NowTicks();
last_tick_was_inactive_ = false;
}
void ReclaimableCodec::OnReclamationPreconditionsUpdated() {
if (AreReclamationPreconditionsMet())
StartIdleReclamationTimer();
else
StopIdleReclamationTimer();
}
bool ReclaimableCodec::AreReclamationPreconditionsMet() {
// If |global_pressure_exceeded_| is true, so should |is_applying_pressure_|.
DCHECK_EQ(global_pressure_exceeded_,
global_pressure_exceeded_ && is_applying_pressure_);
return is_applying_pressure_ && global_pressure_exceeded_ && is_backgrounded_;
}
void ReclaimableCodec::StartIdleReclamationTimer() {
DCHECK(AreReclamationPreconditionsMet());
if (activity_timer_.IsActive())
return;
DVLOG(5) << __func__ << " Starting timer.";
activity_timer_.StartRepeating(inactivity_threshold_ / 2, FROM_HERE);
}
void ReclaimableCodec::StopIdleReclamationTimer() {
DCHECK(!AreReclamationPreconditionsMet());
activity_timer_.Stop();
}
void ReclaimableCodec::OnActivityTimerFired(TimerBase*) {
DCHECK(AreReclamationPreconditionsMet());
auto time_inactive = tick_clock_->NowTicks() - last_activity_;
bool is_inactive = time_inactive >= inactivity_threshold_;
// Do not immediately reclaim. Make sure the codec is inactive for 2 ticks.
// Otherwise, tabs that were suspended could see their codecs reclaimed
// immediately after being resumed.
if (is_inactive && last_tick_was_inactive_) {
activity_timer_.Stop();
auto* message = "Codec reclaimed due to inactivity.";
if (RuntimeEnabledFeatures::QuotaExceededErrorUpdateEnabled()) {
OnCodecReclaimed(MakeGarbageCollected<QuotaExceededError>(message));
} else {
OnCodecReclaimed(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kQuotaExceededError, message));
}
}
last_tick_was_inactive_ = time_inactive >= (inactivity_threshold_ / 2);
}
CodecPressureManager* ReclaimableCodec::PressureManager() {
auto* execution_context = GetExecutionContext();
if (!execution_context || execution_context->IsContextDestroyed())
return nullptr;
auto& manager_provider =
CodecPressureManagerProvider::From(*execution_context);
switch (codec_type_) {
case CodecType::kDecoder:
return manager_provider.GetDecoderPressureManager();
case CodecType::kEncoder:
return manager_provider.GetEncoderPressureManager();
}
}
} // namespace blink