| // 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 "components/reporting/util/disconnectable_client.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <queue> |
| #include <utility> |
| |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "components/reporting/util/reporting_errors.h" |
| #include "components/reporting/util/status.h" |
| |
| namespace reporting { |
| |
| // The feature is enabled by default, to be disabled as a kill switch only |
| // (if it is disabled, all jobs are permitted to run, no limit) |
| BASE_FEATURE(kEnableReportingDelegateJobsLimit, |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| constexpr char kMaxDelegatesRunningParamName[] = "max_running"; |
| |
| const base::FeatureParam<size_t> kMaxDelegatesRunning{ |
| &kEnableReportingDelegateJobsLimit, kMaxDelegatesRunningParamName, 10}; |
| |
| namespace { |
| |
| size_t max_delegate_jobs_running() { |
| if (!base::FeatureList::IsEnabled(kEnableReportingDelegateJobsLimit)) { |
| return std::numeric_limits<size_t>::max(); |
| } |
| return kMaxDelegatesRunning.Get(); |
| } |
| |
| } // namespace |
| |
| DisconnectableClient::DisconnectableClient( |
| scoped_refptr<base::SequencedTaskRunner> task_runner) |
| : task_runner_(task_runner) {} |
| |
| DisconnectableClient::~DisconnectableClient() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| SetAvailability(/*is_available=*/false); |
| } |
| |
| // static |
| const base::Feature& DisconnectableClient::ReportingDelegateJobsLimitFeature() { |
| return kEnableReportingDelegateJobsLimit; |
| } |
| |
| // static |
| const char* DisconnectableClient::MaxDelegatesRunningParamName() { |
| return kMaxDelegatesRunningParamName; |
| } |
| |
| void DisconnectableClient::MaybeMakeCall(std::unique_ptr<Delegate> delegate) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Bail out, if missive daemon is not available over dBus. |
| if (!is_available_) { |
| delegate->Respond(Status(error::UNAVAILABLE, |
| disconnectable_client::kErrorServiceUnavailable)); |
| base::UmaHistogramEnumeration( |
| reporting::kUmaUnavailableErrorReason, |
| UnavailableErrorReason::CLIENT_NOT_CONNECTED_TO_MISSIVE, |
| UnavailableErrorReason::MAX_VALUE); |
| return; |
| } |
| |
| // Check whether there are too many delegates running already. |
| if (outstanding_delegates_.size() >= max_delegate_jobs_running()) { |
| // Delay. |
| delayed_delegates_.push(std::move(delegate)); |
| return; |
| } |
| |
| // Otherwise call it. |
| MakeCall(std::move(delegate)); |
| } |
| |
| void DisconnectableClient::MakeCall(std::unique_ptr<Delegate> delegate) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Bail out, if missive daemon is not available over dBus. |
| if (!is_available_) { |
| delegate->Respond(Status(error::UNAVAILABLE, |
| disconnectable_client::kErrorServiceUnavailable)); |
| base::UmaHistogramEnumeration( |
| reporting::kUmaUnavailableErrorReason, |
| UnavailableErrorReason::CLIENT_NOT_CONNECTED_TO_MISSIVE, |
| UnavailableErrorReason::MAX_VALUE); |
| return; |
| } |
| // Add the delegate to the map. |
| const auto id = ++last_id_; |
| auto res = outstanding_delegates_.emplace(id, std::move(delegate)); |
| // Make a call, resume on CallResponded, when response is received. |
| res.first->second->DoCall(base::BindOnce(&DisconnectableClient::CallResponded, |
| weak_ptr_factory_.GetWeakPtr(), id)); |
| } |
| |
| void DisconnectableClient::CallResponded(uint64_t id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto it = outstanding_delegates_.find(id); |
| if (it == outstanding_delegates_.end()) { |
| // Callback has already been removed, no action needed. |
| return; |
| } |
| // Remove delegate from |outstanding_delegates_|. |
| const auto delegate = std::move(it->second); |
| outstanding_delegates_.erase(it); |
| // Respond through the |delegate|. |
| delegate->Respond(Status::StatusOK()); |
| // If there are more delegates in queue, run the first one or more. |
| // Note that we check the count again, since `max_delegate_jobs_running` |
| // could have changed. |
| while (!delayed_delegates_.empty() && |
| outstanding_delegates_.size() < max_delegate_jobs_running()) { |
| MakeCall(std::move(delayed_delegates_.front())); |
| delayed_delegates_.pop(); |
| } |
| } |
| |
| void DisconnectableClient::SetAvailability(bool is_available) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| is_available_ = is_available; |
| LOG(WARNING) << "Service became " << (is_available_ ? "" : "un") |
| << "available"; |
| if (is_available_) { |
| // Start delayed calls, if there are spots. |
| while (!delayed_delegates_.empty() && |
| outstanding_delegates_.size() < max_delegate_jobs_running()) { |
| MakeCall(std::move(delayed_delegates_.front())); |
| delayed_delegates_.pop(); |
| } |
| } else { |
| // Cancel all pending calls. |
| while (!outstanding_delegates_.empty()) { |
| // Remove the first delegate from |outstanding_delegates_|. |
| const auto delegate = std::move(outstanding_delegates_.begin()->second); |
| outstanding_delegates_.erase(outstanding_delegates_.begin()); |
| // Respond through the |delegate|. |
| delegate->Respond(Status( |
| error::UNAVAILABLE, disconnectable_client::kErrorServiceUnavailable)); |
| base::UmaHistogramEnumeration( |
| reporting::kUmaUnavailableErrorReason, |
| UnavailableErrorReason::CLIENT_NOT_CONNECTED_TO_MISSIVE, |
| UnavailableErrorReason::MAX_VALUE); |
| } |
| // Cancel all delayed calls. |
| while (!delayed_delegates_.empty()) { |
| // Remove the first delegate from |delayed_delegates_|. |
| const auto delegate = std::move(delayed_delegates_.front()); |
| delayed_delegates_.pop(); |
| // Respond through the |delegate|. |
| delegate->Respond(Status( |
| error::UNAVAILABLE, disconnectable_client::kErrorServiceUnavailable)); |
| base::UmaHistogramEnumeration( |
| reporting::kUmaUnavailableErrorReason, |
| UnavailableErrorReason::CLIENT_NOT_CONNECTED_TO_MISSIVE, |
| UnavailableErrorReason::MAX_VALUE); |
| } |
| } |
| } |
| |
| } // namespace reporting |