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

media / capabilities / pending_operations.cc [blame]

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/capabilities/pending_operations.h"

#include <string>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/not_fatal_until.h"
#include "base/task/single_thread_task_runner.h"

namespace media {
namespace {
// Timeout threshold for DB operations. See OnOperationTimeout().
// NOTE: Used by UmaHistogramOpTime. Change the name if you change the time.
static constexpr base::TimeDelta kPendingOpTimeout = base::Seconds(30);
}  // namespace

PendingOperations::PendingOperation::PendingOperation(
    std::string uma_str,
    std::unique_ptr<base::CancelableOnceClosure> timeout_closure)
    : uma_str_(std::move(uma_str)),
      timeout_closure_(std::move(timeout_closure)),
      start_ticks_(base::TimeTicks::Now()) {
  DVLOG(3) << __func__ << " Started " << uma_str_;
}

PendingOperations::PendingOperation::~PendingOperation() {
  // Destroying a pending operation that hasn't timed out yet implies the
  // operation has completed.
  if (timeout_closure_ && !timeout_closure_->IsCancelled()) {
    base::TimeDelta op_duration = base::TimeTicks::Now() - start_ticks_;
    UmaHistogramOpTime(op_duration);
    DVLOG(3) << __func__ << " Completed " << uma_str_ << " ("
             << op_duration.InMilliseconds() << ")";

    // Ensure the timeout doesn't fire. Destruction should cancel the callback
    // implicitly, but that's not a documented contract, so just taking the safe
    // route.
    timeout_closure_->Cancel();
  }
}

void PendingOperations::PendingOperation::UmaHistogramOpTime(
    base::TimeDelta duration) {
  base::UmaHistogramCustomMicrosecondsTimes(
      uma_str_, duration, base::Milliseconds(1), kPendingOpTimeout, 50);
}

void PendingOperations::PendingOperation::OnTimeout() {
  UmaHistogramOpTime(kPendingOpTimeout);
  LOG(WARNING) << " Timeout performing " << uma_str_
               << " operation on WebrtcVideoStatsDB";

  // Cancel the closure to ensure we don't double report the task as completed
  // in ~PendingOperation().
  timeout_closure_->Cancel();
}

PendingOperations::PendingOperations(std::string uma_prefix)
    : uma_prefix_(std::move(uma_prefix)) {}

PendingOperations::~PendingOperations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

PendingOperations::Id PendingOperations::Start(std::string_view uma_str) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  Id op_id = next_op_id_++;

  auto timeout_closure = std::make_unique<base::CancelableOnceClosure>(
      base::BindOnce(&PendingOperations::OnTimeout,
                     weak_ptr_factory_.GetWeakPtr(), op_id));

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, timeout_closure->callback(), kPendingOpTimeout);

  pending_ops_.emplace(op_id, std::make_unique<PendingOperation>(
                                  uma_prefix_ + std::string(uma_str),
                                  std::move(timeout_closure)));

  return op_id;
}

void PendingOperations::Complete(Id op_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Destructing the PendingOperation will trigger UMA for completion timing.
  int count = pending_ops_.erase(op_id);

  // No big deal, but very unusual. Timeout is very generous, so tasks that
  // timeout are generally assumed to be permanently hung.
  if (!count)
    DVLOG(2) << __func__ << " DB operation completed after timeout.";
}

void PendingOperations::OnTimeout(Id op_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto it = pending_ops_.find(op_id);
  CHECK(it != pending_ops_.end(), base::NotFatalUntil::M130);

  it->second->OnTimeout();
  pending_ops_.erase(it);
}

}  // namespace media