910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/events/event_ack_data.h"

#include <string>
#include <utility>

#include "base/containers/map_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/uuid.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_external_request_result.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_event_histogram_value.h"

namespace extensions {

EventAckData::EventInfo::EventInfo(
    const base::Uuid& request_uuid,
    int render_process_id,
    int64_t version_id,
    int worker_thread_id,
    bool start_ok,
    content::ServiceWorkerExternalRequestResult external_request_result,
    base::TimeTicks dispatch_start_time,
    EventDispatchSource dispatch_source,
    bool lazy_background_active_on_dispatch,
    const events::HistogramValue histogram_value)
    : request_uuid(request_uuid),
      render_process_id(render_process_id),
      version_id(version_id),
      worker_thread_id(worker_thread_id),
      start_ok(start_ok),
      external_request_result(external_request_result),
      dispatch_start_time(dispatch_start_time),
      dispatch_source(dispatch_source),
      lazy_background_active_on_dispatch(lazy_background_active_on_dispatch),
      histogram_value(histogram_value) {}

EventAckData::EventInfo::EventInfo(EventInfo&& other) = default;

EventAckData::EventAckData() = default;

EventAckData::~EventAckData() = default;

void EventAckData::IncrementInflightEvent(
    content::ServiceWorkerContext* context,
    int render_process_id,
    int64_t version_id,
    int worker_thread_id,
    int event_id,
    base::TimeTicks dispatch_start_time,
    EventDispatchSource dispatch_source,
    bool lazy_background_active_on_dispatch,
    events::HistogramValue histogram_value) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  base::Uuid request_uuid = base::Uuid::GenerateRandomV4();
  bool start_ok = true;

  content::ServiceWorkerExternalRequestResult external_request_result =
      context->StartingExternalRequest(
          version_id,
          content::ServiceWorkerExternalRequestTimeoutType::kDefault,
          request_uuid);
  base::UmaHistogramEnumeration(
      "Extensions.ServiceWorkerBackground.StartingExternalRequest_Result",
      external_request_result);
  if (external_request_result !=
      content::ServiceWorkerExternalRequestResult::kOk) {
    LOG(ERROR) << "StartExternalRequest failed: "
               << static_cast<int>(external_request_result);
    start_ok = false;
  }

  auto insert_result = unacked_events_.try_emplace(
      event_id, EventInfo{request_uuid, render_process_id, version_id,
                          worker_thread_id, start_ok, external_request_result,
                          dispatch_start_time, dispatch_source,
                          lazy_background_active_on_dispatch, histogram_value});
  DCHECK(insert_result.second) << "EventAckData: Duplicate event_id.";

  if (dispatch_source == EventDispatchSource::kDispatchEventToProcess) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&EventAckData::EmitLateAckedEventTask,
                       weak_factory_.GetWeakPtr(), event_id),
        kEventAckMetricTimeLimit);
  }
}

void EventAckData::EmitLateAckedEventTask(int event_id) {
  // If the event is still present then we haven't received the ack yet in
  // `EventAckData::DecrementInflightEvent()`.
  if (auto* value = base::FindOrNull(unacked_events_, event_id)) {
    base::UmaHistogramBoolean(
        "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
        false);
    EmitLateAckedEventTaskMetrics(*value);
  }
}

// Emit metrics helpful in determining causes of `unacked_events_` that are not
// acked within the timeout.
void EventAckData::EmitLateAckedEventTaskMetrics(const EventInfo& event_info) {
  base::UmaHistogramEnumeration(
      "Extensions.Events.ServiceWorkerDispatchFailed.Event",
      event_info.histogram_value, events::ENUM_BOUNDARY);

  base::UmaHistogramBoolean(
      "Extensions.Events.ServiceWorkerDispatchFailed.StartExternalRequestOk",
      event_info.start_ok);
  if (!event_info.start_ok) {
    base::UmaHistogramEnumeration(
        "Extensions.Events.ServiceWorkerDispatchFailed."
        "StartExternalRequestResult",
        event_info.external_request_result);
  }

  // TODO(crbug.com/40909770): Implement service worker running status as a late
  // acked event metric when it can be more accurately determined. For example,
  // it could be useful to determine if the late acked events are for already
  // shut down workers and therefore wouldn't be "late".
}

// static
void EventAckData::EmitDispatchTimeMetrics(EventInfo& event_info) {
  // Only emit events that use the EventRouter::DispatchEventToProcess() event
  // routing flow since EventRouter::DispatchEventToSender() uses a different
  // flow that doesn't include dispatch start and service worker start time.
  if (event_info.dispatch_source ==
      EventDispatchSource::kDispatchEventToProcess) {
    base::UmaHistogramCustomMicrosecondsTimes(
        "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
        /*sample=*/base::TimeTicks::Now() - event_info.dispatch_start_time,
        /*min=*/base::Microseconds(1), /*max=*/base::Minutes(5),
        /*buckets=*/100);
    const char* active_metric_name =
        event_info.lazy_background_active_on_dispatch
            ? "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2."
              "Active3"
            : "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2."
              "Inactive3";
    base::UmaHistogramCustomMicrosecondsTimes(
        active_metric_name,
        /*sample=*/base::TimeTicks::Now() - event_info.dispatch_start_time,
        /*min=*/base::Microseconds(1), /*max=*/base::Minutes(5),
        /*buckets=*/100);

    base::UmaHistogramCustomTimes(
        "Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2",
        /*sample=*/base::TimeTicks::Now() - event_info.dispatch_start_time,
        /*min=*/base::Seconds(1), /*max=*/base::Days(1),
        /*buckets=*/100);

    // Emit only if we're within the expected event ack time limit. We'll take
    // care of the emit for a late ack via a delayed task.
    bool late_ack = (base::TimeTicks::Now() - event_info.dispatch_start_time) >
                    kEventAckMetricTimeLimit;
    if (!late_ack) {
      base::UmaHistogramBoolean(
          "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
          true);
    }
  }
}

void EventAckData::DecrementInflightEvent(
    content::ServiceWorkerContext* context,
    int render_process_id,
    int64_t version_id,
    int worker_thread_id,
    int event_id,
    bool worker_stopped,
    base::OnceClosure failure_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Event not found.
  auto request_info_iter = unacked_events_.find(event_id);
  if (request_info_iter == unacked_events_.end()) {
    if (worker_stopped) {
      // If the worker has stopped, it's possible the event was already cleaned
      // up by `ClearUnackedEventsForWorker` just before the ack arrived.
      return;
    }
    // Event missing from a running worker. Bad ack.
    std::move(failure_callback).Run();
    return;
  }

  // Event found.
  EventInfo& event_info = request_info_iter->second;
  if (event_info.render_process_id != render_process_id ||
      event_info.version_id != version_id ||
      event_info.worker_thread_id != worker_thread_id) {
    // Mismatched worker. This is always a bad ack, regardless of whether
    // the sender is stopped or running.
    std::move(failure_callback).Run();
    return;
  }

  EmitDispatchTimeMetrics(event_info);

  base::Uuid request_uuid = std::move(event_info.request_uuid);
  bool start_ok = event_info.start_ok;
  unacked_events_.erase(request_info_iter);

  content::ServiceWorkerExternalRequestResult result =
      context->FinishedExternalRequest(version_id, request_uuid);
  base::UmaHistogramEnumeration(
      "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result",
      result);
  // If the worker was already stopped or StartExternalRequest didn't succeed,
  // the FinishedExternalRequest will legitimately fail.
  if (worker_stopped || !start_ok) {
    return;
  }

  base::UmaHistogramEnumeration(
      "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result_"
      "PostReturn",
      result);

  switch (result) {
    case content::ServiceWorkerExternalRequestResult::kOk:
    // Metrics have shown us that it is possible that a worker may not be found
    // or not running at this point.
    case content::ServiceWorkerExternalRequestResult::kWorkerNotFound:
    case content::ServiceWorkerExternalRequestResult::kWorkerNotRunning:
    // Null context can happen in the rare case if ServiceWorkerContextCore is
    // torn down when EventRouter + BrowserContext are still alive and an
    // event happens to be acked here.
    case content::ServiceWorkerExternalRequestResult::kNullContext:
      // TODO(crbug.com/41494056): Perform more graceful shutdown when
      // ServiceWorkerContextCore is torn down.

    // kBadRequestId can expectedly happen if a new instance of a worker starts
    // while an ack for the previous worker is in-flight to the browser. We then
    // receive the ack and ServiceWorkerContext can't find the
    // external/in-flight request because the previous worker's
    // `ServiceWorkerVersion` has been replaced by the new worker's
    // `ServiceWorkerVersion`. The new version then does not have a record of
    // the external/in-flight request and returns kBadRequestId.
    case content::ServiceWorkerExternalRequestResult::kBadRequestId:
      // TODO(crbug.com/40072982): Reliably detect when the above occurs and
      // continue to not kill the renderer. But if the event is not for an old
      // instance of the worker then consider CHECK()-ing since this could
      // indicate a bug in the tracking of external requests in the browser.
      break;
  }
}

void EventAckData::ClearUnackedEventsForRenderProcess(int render_process_id) {
  std::erase_if(unacked_events_, [render_process_id](const auto& entry) {
    return entry.second.render_process_id == render_process_id;
  });
}

void EventAckData::ClearUnackedEventsForWorker(
    content::ServiceWorkerContext* context,
    int render_process_id,
    int64_t version_id,
    int worker_thread_id) {
  // Iterate manually because we need to access the EventInfo (to retrieve
  // `request_uuid`) before erasing the element.
  for (auto it = unacked_events_.begin(); it != unacked_events_.end();) {
    const EventInfo& event_info = it->second;
    if (event_info.render_process_id != render_process_id ||
        event_info.version_id != version_id ||
        event_info.worker_thread_id != worker_thread_id) {
      it++;
      continue;
    }

    // The worker stopped before acking the event. Notify SWContext that the
    // external request finished. This balances the `StartingExternalRequest`
    // call in `IncrementInflightEvent`.
    if (context && event_info.start_ok) {
      context->FinishedExternalRequest(event_info.version_id,
                                       event_info.request_uuid);
    }
    it = unacked_events_.erase(it);
  }
}

bool EventAckData::HasUnackedEventForTesting(int event_id) {
  return base::FindOrNull(unacked_events_, event_id);
}

}  // namespace extensions