blob: 989e8ac857bf1cdf09acee256af5feb2d444a4d3 [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 "content/browser/interest_group/auction_process_manager.h"
#include <optional>
#include <string>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/trusted_signals_cache_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/process_allocation_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "url/origin.h"
namespace content {
namespace {
void RecordRequestWorkletServiceOutcomeUMA(
AuctionProcessManager::WorkletType worklet_type,
AuctionProcessManager::RequestWorkletServiceOutcome result) {
base::UmaHistogramEnumeration(
base::StrCat({"Ads.InterestGroup.Auction.",
worklet_type == AuctionProcessManager::WorkletType::kSeller
? "Seller."
: "Buyer.",
"RequestWorkletServiceOutcome"}),
result);
}
void RecordIdleProcessExpiredUma(bool result) {
base::UmaHistogramBoolean("Ads.InterestGroup.Auction.IdleProcessExpired",
result);
}
} // namespace
constexpr size_t AuctionProcessManager::kMaxBidderProcesses = 10;
constexpr size_t AuctionProcessManager::kMaxSellerProcesses = 3;
AuctionProcessManager::WorkletProcess::ProcessContext::ProcessContext(
mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> service,
RenderProcessHost* render_process_host)
: service(std::move(service)), render_process_host(render_process_host) {}
AuctionProcessManager::WorkletProcess::ProcessContext::ProcessContext(
ProcessContext&&) = default;
AuctionProcessManager::WorkletProcess::ProcessContext::~ProcessContext() =
default;
AuctionProcessManager::WorkletProcess::WorkletProcess(
AuctionProcessManager* auction_process_manager,
scoped_refptr<SiteInstance> site_instance,
WorkletType worklet_type,
const url::Origin& origin,
bool uses_shared_process,
bool is_idle,
bool is_bound_to_origin)
: site_instance_(std::move(site_instance)),
worklet_type_(worklet_type),
origin_(origin),
start_time_(base::TimeTicks::Now()),
uses_shared_process_(uses_shared_process),
auction_process_manager_(auction_process_manager),
is_idle_(is_idle),
is_bound_to_origin_(is_bound_to_origin) {
DCHECK(auction_process_manager);
// Non-idle processes must be bound.
CHECK(is_idle_ || is_bound_to_origin_);
// Shared processes cannot be idle.
CHECK(!uses_shared_process || !is_idle);
if (is_idle_) {
remove_idle_process_from_manager_timer_.Start(
FROM_HERE,
features::kFledgeStartAnticipatoryProcessExpirationTime.Get(),
base::BindOnce(&RecordIdleProcessExpiredUma, true)
.Then(base::BindOnce(&WorkletProcess::RemoveFromProcessManager,
base::Unretained(this),
/*on_destruction=*/false)));
}
}
auction_worklet::mojom::AuctionWorkletService*
AuctionProcessManager::WorkletProcess::GetService() {
DCHECK(service_.is_connected());
return service_.get();
}
std::optional<base::ProcessId> AuctionProcessManager::WorkletProcess::GetPid(
base::OnceCallback<void(base::ProcessId)> callback) {
if (pid_.has_value()) {
return pid_;
} else {
waiting_for_pid_.push_back(std::move(callback));
return std::nullopt;
}
}
bool AuctionProcessManager::WorkletProcess::HasPid() const {
return pid_.has_value();
}
void AuctionProcessManager::WorkletProcess::OnLaunchedWithProcess(
const base::Process& process) {
base::UmaHistogramTimes("Ads.InterestGroup.Auction.ProcessLaunchTime",
base::TimeTicks::Now() - start_time_);
DCHECK(!pid_.has_value());
base::ProcessId pid = process.Pid();
pid_ = std::make_optional<base::ProcessId>(pid);
std::vector<base::OnceCallback<void(base::ProcessId)>> waiting_for_pid =
std::move(waiting_for_pid_);
for (auto& callback : waiting_for_pid) {
std::move(callback).Run(pid);
}
}
void AuctionProcessManager::WorkletProcess::ReassignWorkletTypeAndOrigin(
AuctionProcessManager::WorkletType worklet_type,
const url::Origin& origin) {
// We should only reassign the worklet type and origin of an unused unbound
// non-shared process.
CHECK(!is_bound_to_origin_);
CHECK(is_idle_);
CHECK(!uses_shared_process_);
worklet_type_ = worklet_type;
origin_ = origin;
}
void AuctionProcessManager::WorkletProcess::ActivateAndBindIfUnbound(
WorkletType worklet_type,
const url::Origin& origin) {
DCHECK(is_idle_);
DCHECK(!uses_shared_process_);
if (is_bound_to_origin_) {
CHECK_EQ(worklet_type, worklet_type_);
CHECK_EQ(origin, origin_);
} else {
ReassignWorkletTypeAndOrigin(worklet_type, origin);
is_bound_to_origin_ = true;
OnBoundToOrigin();
}
is_idle_ = false;
RecordIdleProcessExpiredUma(false);
remove_idle_process_from_manager_timer_.Stop();
}
void AuctionProcessManager::WorkletProcess::SetService(
ProcessContext service_context) {
DCHECK(!service_);
DCHECK(!render_process_host_);
DCHECK(service_context.service);
service_.Bind(std::move(service_context.service));
service_.set_disconnect_handler(
base::BindOnce(&WorkletProcess::RemoveFromProcessManager,
base::Unretained(this), /*on_destruction=*/false));
if (service_context.render_process_host) {
DCHECK(site_instance_);
render_process_host_ = service_context.render_process_host;
render_process_host_->IncrementWorkerRefCount();
render_process_host_->AddObserver(this);
// Note the PID if the process has already launched
if (render_process_host_->IsReady()) {
DCHECK(render_process_host_->GetProcess().IsValid());
pid_ = render_process_host_->GetProcess().Pid();
}
}
if (is_bound_to_origin_) {
OnBoundToOrigin();
}
}
void AuctionProcessManager::WorkletProcess::RenderProcessReady(
RenderProcessHost* host) {
DCHECK(render_process_host_);
DCHECK(render_process_host_->GetProcess().IsValid());
OnLaunchedWithProcess(render_process_host_->GetProcess());
}
void AuctionProcessManager::WorkletProcess::RenderProcessHostDestroyed(
RenderProcessHost* host) {
DCHECK_EQ(host, render_process_host_);
RemoveFromProcessManager(/*on_destruction=*/false);
}
void AuctionProcessManager::WorkletProcess::RemoveFromProcessManager(
bool on_destruction) {
if (render_process_host_) {
render_process_host_->RemoveObserver(this);
if (!render_process_host_->AreRefCountsDisabled()) {
render_process_host_->DecrementWorkerRefCount();
}
render_process_host_ = nullptr;
}
AuctionProcessManager* maybe_apm = auction_process_manager_;
// Clear `auction_process_manager_` to make sure OnWorkletProcessUnusable()
// is called once. Clear it before call to ensure this is the case even
// if this method is re-entered somehow.
auction_process_manager_ = nullptr;
if (maybe_apm) {
if (is_idle_ && !on_destruction) {
// Idle shared worklet processes are not allowed.
DCHECK(!uses_shared_process_);
// AuctionProcessManager owns idle processes, so if this
// process is idle & being destructed, it must have been removed
// already.
maybe_apm->ReleaseIdleProcess(this);
} else if (!is_idle_ && !uses_shared_process_) {
maybe_apm->OnWorkletProcessUnusable(this);
}
}
}
AuctionProcessManager::WorkletProcess::~WorkletProcess() {
RemoveFromProcessManager(/*on_destruction=*/true);
}
void AuctionProcessManager::WorkletProcess::OnBoundToOrigin() {
DCHECK(is_bound_to_origin_);
// If the TrustedSignalsCache exists (and thus is enabled), pass a pipe to
// for KVv2 bidding signals fetches.
auto* trusted_signals_cache =
auction_process_manager_->trusted_signals_cache_.get();
if (trusted_signals_cache) {
service_->SetTrustedSignalsCache(trusted_signals_cache->CreateRemote(
worklet_type_ == WorkletType::kBidder
? TrustedSignalsCacheImpl::SignalsType::kBidding
: TrustedSignalsCacheImpl::SignalsType::kScoring,
origin_));
}
}
AuctionProcessManager::ProcessHandle::ProcessHandle() = default;
AuctionProcessManager::ProcessHandle::~ProcessHandle() {
if (worklet_process_) {
// Make sure the worklet process was not reassigned an origin or type
// while this ProcessHandle was alive.
DCHECK_EQ(worklet_process_->origin(), origin_);
DCHECK_EQ(worklet_process_->worklet_type(), worklet_type_);
}
if (manager_) {
// `manager_` should only be non-null if the handle is waiting for a
// process.
DCHECK(callback_);
manager_->RemovePendingProcessHandle(this);
}
}
auction_worklet::mojom::AuctionWorkletService*
AuctionProcessManager::ProcessHandle::GetService() {
if (!worklet_process_ || callback_)
return nullptr;
return worklet_process_->GetService();
}
RenderProcessHost*
AuctionProcessManager::ProcessHandle::GetRenderProcessHostForTesting() {
if (!worklet_process_)
return nullptr;
return worklet_process_->render_process_host();
}
std::optional<base::ProcessId> AuctionProcessManager::ProcessHandle::GetPid(
base::OnceCallback<void(base::ProcessId)> callback) {
DCHECK(worklet_process_);
return worklet_process_->GetPid(std::move(callback));
}
void AuctionProcessManager::ProcessHandle::AssignProcess(
scoped_refptr<WorkletProcess> worklet_process) {
// Only a process matching in origin and type can be assigned.
DCHECK_EQ(worklet_process->origin(), origin_);
DCHECK_EQ(worklet_process->worklet_type(), worklet_type_);
worklet_process_ = std::move(worklet_process);
// No longer needed.
manager_ = nullptr;
if (callback_) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ProcessHandle::InvokeCallback,
weak_ptr_factory_.GetWeakPtr()));
}
}
void AuctionProcessManager::ProcessHandle::OnBaseProcessLaunchedForTesting(
const base::Process& process) const {
if (worklet_process_) {
worklet_process_->OnLaunchedWithProcess(process);
}
}
void AuctionProcessManager::ProcessHandle::InvokeCallback() {
DCHECK(callback_);
std::move(callback_).Run();
}
AuctionProcessManager::~AuctionProcessManager() {
DCHECK(pending_bidder_request_queue_.empty());
DCHECK(pending_seller_request_queue_.empty());
DCHECK(pending_bidder_requests_.empty());
DCHECK(pending_seller_requests_.empty());
DCHECK(bidder_processes_.empty());
DCHECK(seller_processes_.empty());
}
bool AuctionProcessManager::RequestWorkletService(
WorkletType worklet_type,
const url::Origin& origin,
scoped_refptr<SiteInstance> frame_site_instance,
ProcessHandle* process_handle,
base::OnceClosure callback) {
DCHECK(!callback.is_null());
// `process_handle` should not already be in use.
DCHECK(!process_handle->manager_);
DCHECK(!process_handle->callback_);
DCHECK(!process_handle->worklet_process_);
process_handle->manager_ = this;
process_handle->origin_ = origin;
process_handle->worklet_type_ = worklet_type;
process_handle->site_instance_ =
MaybeComputeSiteInstance(frame_site_instance.get(), origin);
// See if a subclass can reuse existing non-auction process for this.
//
// This needs to be done before TryCreateOrGetProcessForHandle, since
// shared processes really can't be keyed by origin.
if (TryUseSharedProcess(process_handle)) {
RecordRequestWorkletServiceOutcomeUMA(
worklet_type, RequestWorkletServiceOutcome::kUsedSharedProcess);
return true;
}
// If can assign a process to the handle instantly, nothing else to do.
RequestWorkletServiceOutcome create_or_get_process_outcome =
TryCreateOrGetProcessForHandle(process_handle);
RecordRequestWorkletServiceOutcomeUMA(worklet_type,
create_or_get_process_outcome);
if (create_or_get_process_outcome !=
RequestWorkletServiceOutcome::kHitProcessLimit) {
return true;
}
PendingRequestQueue* pending_requests = GetPendingRequestQueue(worklet_type);
pending_requests->push_back(process_handle);
process_handle->queued_request_ = std::prev(pending_requests->end());
process_handle->callback_ = std::move(callback);
// Pending requests are also tracked in a map, to aid in the bidder process
// assignment logic.
(*GetPendingRequestMap(worklet_type))[origin].insert(process_handle);
return false;
}
void AuctionProcessManager::MaybeStartAnticipatoryProcess(
const url::Origin& origin,
SiteInstance* frame_site_instance,
WorkletType worklet_type) {
if (!base::FeatureList::IsEnabled(
features::kFledgeStartAnticipatoryProcesses)) {
return;
}
// Don't start a process if we can use a shared process.
// `site_instance` will be null if we're using dedicated utility processes
// only.
scoped_refptr<SiteInstance> site_instance =
MaybeComputeSiteInstance(frame_site_instance, origin);
if (site_instance && !site_instance->RequiresDedicatedProcess()) {
return;
}
// Do not create a process for this origin/worklet_type if there already is
// one (active or idle).
ProcessMap* processes = Processes(worklet_type);
auto process_it = processes->find(origin);
if (process_it != processes->end()) {
return;
}
// Keep track of the number of idle processes of this type.
size_t num_idle_processes = 0;
for (const scoped_refptr<WorkletProcess>& process : idle_processes_) {
if (process->worklet_type() == worklet_type) {
if (process->origin() == origin) {
return;
}
num_idle_processes += 1;
}
}
// Don't start a process if we've already hit the process limit.
if (!HasAvailableProcessSlotForIdleProcess(worklet_type,
num_idle_processes)) {
return;
}
scoped_refptr<WorkletProcess> worklet_process = LaunchProcess(
worklet_type, origin, std::move(site_instance), /*is_idle=*/true);
idle_processes_.push_back(std::move(worklet_process));
}
AuctionProcessManager::RequestWorkletServiceOutcome
AuctionProcessManager::TryCreateOrGetProcessForHandle(
ProcessHandle* process_handle) {
// Look for a pre-existing matching process.
ProcessMap* processes = Processes(process_handle->worklet_type_);
auto process_it = processes->find(process_handle->origin_);
if (process_it != processes->end()) {
// If there's a matching process, assign it.
process_handle->AssignProcess(WrapRefCounted(process_it->second));
return RequestWorkletServiceOutcome::kUsedExistingDedicatedProcess;
}
// If the corresponding process limit has been hit, can't create a new
// process.
if (!HasAvailableProcessSlotForActiveProcess(process_handle->worklet_type_)) {
return RequestWorkletServiceOutcome::kHitProcessLimit;
}
if (TryToUseIdleProcessForHandle(process_handle)) {
return RequestWorkletServiceOutcome::kUsedIdleProcess;
}
// Making a new process shouldn't cause us to exceed the permitted number
// of idle processes.
DCHECK(HasAvailableProcessSlotForIdleProcess(
process_handle->worklet_type(),
std::count_if(
idle_processes_.begin(), idle_processes_.end(),
[&process_handle](const scoped_refptr<WorkletProcess>& process) {
return process->worklet_type() == process_handle->worklet_type();
})));
// Launch the process and create WorkletProcess object bound to it.
scoped_refptr<WorkletProcess> worklet_process =
LaunchProcess(process_handle->worklet_type_, process_handle->origin_,
process_handle->site_instance_,
/*is_idle=*/false);
(*processes)[process_handle->origin_] = worklet_process.get();
process_handle->AssignProcess(std::move(worklet_process));
OnNewProcessAssigned(process_handle);
return RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess;
}
bool AuctionProcessManager::TryToUseIdleProcessForHandle(
ProcessHandle* process_handle) {
// This function shouldn't be called unless we have a spot for a new process.
DCHECK(
HasAvailableProcessSlotForActiveProcess(process_handle->worklet_type_));
if (idle_processes_.empty()) {
return false;
}
// Keep track of processes we may want to use for the handle. Prefer to use a
// process that has already launched, and if there is none, a process that
// was created the earliest possible. idle_processes_ is sorted by process
// creation time.
auto process_matching_origin_and_type = idle_processes_.end();
auto first_process_matching_type = idle_processes_.end();
auto best_unbound_process_matching_type = idle_processes_.end();
auto best_unbound_process = idle_processes_.end();
size_t num_idle_processes_of_type = 0;
for (auto it = idle_processes_.begin(); it != idle_processes_.end(); ++it) {
if (it->get()->worklet_type() == process_handle->worklet_type()) {
num_idle_processes_of_type++;
if (it->get()->origin() == process_handle->origin()) {
process_matching_origin_and_type = it;
}
if (first_process_matching_type == idle_processes_.end()) {
first_process_matching_type = it;
}
if (!it->get()->is_bound_to_origin() &&
(best_unbound_process_matching_type == idle_processes_.end() ||
(!best_unbound_process_matching_type->get()->HasPid() &&
it->get()->HasPid()))) {
best_unbound_process_matching_type = it;
}
}
if (!it->get()->is_bound_to_origin() &&
(best_unbound_process == idle_processes_.end() ||
(!best_unbound_process->get()->HasPid() && it->get()->HasPid()))) {
best_unbound_process = it;
}
}
auto idle_process_to_use = idle_processes_.end();
if (process_matching_origin_and_type != idle_processes_.end()) {
// If we have a perfectly matching process that is bound
// to its origin, prefer to use it. If it's unbound, prefer to
// use the first unbound process (because it was created earlier).
if (process_matching_origin_and_type->get()->is_bound_to_origin() ||
process_matching_origin_and_type == best_unbound_process) {
idle_process_to_use = process_matching_origin_and_type;
} else {
// There's at least 1 unbound process because
// `process_matching_origin_and_type` is unbound. We can use the first
// unbound process no matter its type because we can't go over the process
// limit if the `process_handle`'s origin and type are already in
// `idle_processes_`.
CHECK(best_unbound_process != idle_processes_.end());
idle_process_to_use = best_unbound_process;
// We want the `best_unbound_process` origin and type to remain in
// `idle_processes_` so we can confirm we've already started
// a process for that origin and type.
process_matching_origin_and_type->get()->ReassignWorkletTypeAndOrigin(
best_unbound_process->get()->worklet_type(),
best_unbound_process->get()->origin());
}
} else {
if (HasAvailableProcessSlotForIdleProcess(process_handle->worklet_type(),
num_idle_processes_of_type)) {
// We can use the first unbound process regardless of its `worklet_type`
// since we have an available spot of `worklet_type.`
if (best_unbound_process == idle_processes_.end()) {
return false;
}
idle_process_to_use = best_unbound_process;
} else {
// There must be a `first_process_matching_type` because
// HasAvailableProcessSlotForActiveProcess() is true but
// HasAvailableProcessSlotForIdleProcess() is false.
CHECK(first_process_matching_type != idle_processes_.end());
if (best_unbound_process_matching_type != idle_processes_.end()) {
idle_process_to_use = best_unbound_process_matching_type;
} else {
// There's no process we can use without going over the limit.
// Remove a process so we can create a new one.
idle_processes_.erase(first_process_matching_type);
return false;
}
}
}
CHECK(idle_process_to_use != idle_processes_.end());
idle_process_to_use->get()->ActivateAndBindIfUnbound(
process_handle->worklet_type(), process_handle->origin());
process_handle->AssignProcess(idle_process_to_use->get());
ProcessMap* processes = Processes(process_handle->worklet_type_);
(*processes)[process_handle->origin_] = idle_process_to_use->get();
idle_processes_.erase(idle_process_to_use);
return true;
}
AuctionProcessManager::AuctionProcessManager::AuctionProcessManager(
TrustedSignalsCacheImpl* trusted_signals_cache)
: trusted_signals_cache_(trusted_signals_cache) {}
void AuctionProcessManager::RemovePendingProcessHandle(
ProcessHandle* process_handle) {
DCHECK(!process_handle->worklet_process_);
// Remove the ProcessHandle from internal data structure(s) tracking it. No
// need to do anything else, as the handle hadn't yet been assigned a process.
PendingRequestQueue* pending_request_queue =
GetPendingRequestQueue(process_handle->worklet_type_);
pending_request_queue->erase(process_handle->queued_request_);
// Clear the iterator, which will hopefully make crashes more likely if it's
// accidentally used again.
process_handle->queued_request_ = PendingRequestQueue::iterator();
// Requests must also be removed from the map.
PendingRequestMap* pending_request_map =
GetPendingRequestMap(process_handle->worklet_type_);
auto it = pending_request_map->find(process_handle->origin_);
CHECK(it != pending_request_map->end());
DCHECK_EQ(1u, it->second.count(process_handle));
it->second.erase(process_handle);
// If there are no more pending requests for the same origin, remove the
// origin's entry in `pending_request_map` as well.
if (it->second.empty())
pending_request_map->erase(it);
}
void AuctionProcessManager::OnWorkletProcessUnusable(
WorkletProcess* worklet_process) {
ProcessMap* processes = Processes(worklet_process->worklet_type());
auto it = processes->find(worklet_process->origin());
CHECK(it != processes->end());
processes->erase(it);
// May need to launch another process at this point.
// Since a process was just destroyed, there should be at least one available
// slot to create another.
DCHECK(
HasAvailableProcessSlotForActiveProcess(worklet_process->worklet_type()));
// If there are no pending requests for the corresponding worklet type,
// nothing more to do.
PendingRequestQueue* queue =
GetPendingRequestQueue(worklet_process->worklet_type());
if (queue->empty())
return;
// All the pending requests for the same origin as the oldest pending request.
std::set<raw_ptr<ProcessHandle, SetExperimental>>* pending_requests =
&(*GetPendingRequestMap(
worklet_process->worklet_type()))[queue->front()->origin_];
// Walk through all requests that can be served by the same process as the
// next bidder process in the queue, assigning them a process. This code does
// not walk through them in FIFO order. Network response order matters most
// here, but that will likely be influenced by callback invocation order.
//
// TODO(mmenke): Consider assigning processes to these matching requests in
// FIFO order.
// Have to record the number of requests and iterate on that, as
// `pending_requests` will be deleted when the last request is removed.
size_t num_matching_requests = pending_requests->size();
DCHECK_GT(num_matching_requests, 0u);
while (num_matching_requests > 0) {
ProcessHandle* process_handle = *pending_requests->begin();
RemovePendingProcessHandle(process_handle);
// This should always succeed for the first request because there's an
// available process slot. Subsequent requests will just receive the process
// created for the first request. Could cache the process returned by the
// first request and reuse it, but doesn't seem worth the effort.
bool process_created = TryCreateOrGetProcessForHandle(process_handle) !=
RequestWorkletServiceOutcome::kHitProcessLimit;
CHECK(process_created);
--num_matching_requests;
// Nothing else to do after assigning the process - assigning a process
// results in the callback being invoked asynchronously.
}
}
void AuctionProcessManager::ReleaseIdleProcess(
AuctionProcessManager::WorkletProcess* worklet_process) {
std::erase_if(idle_processes_,
[worklet_process](const scoped_refptr<WorkletProcess>& item) {
return item.get() == worklet_process;
});
}
AuctionProcessManager::PendingRequestQueue*
AuctionProcessManager::GetPendingRequestQueue(WorkletType worklet_type) {
if (worklet_type == WorkletType::kBidder)
return &pending_bidder_request_queue_;
return &pending_seller_request_queue_;
}
AuctionProcessManager::ProcessMap* AuctionProcessManager::Processes(
WorkletType worklet_type) {
if (worklet_type == WorkletType::kBidder)
return &bidder_processes_;
return &seller_processes_;
}
AuctionProcessManager::PendingRequestMap*
AuctionProcessManager::GetPendingRequestMap(WorkletType worklet_type) {
if (worklet_type == WorkletType::kBidder)
return &pending_bidder_requests_;
return &pending_seller_requests_;
}
bool AuctionProcessManager::HasAvailableProcessSlotForActiveProcess(
WorkletType worklet_type) const {
if (worklet_type == WorkletType::kBidder)
return bidder_processes_.size() < kMaxBidderProcesses;
return seller_processes_.size() < kMaxSellerProcesses;
}
bool AuctionProcessManager::HasAvailableProcessSlotForIdleProcess(
WorkletType worklet_type,
size_t num_idle_processes_of_type) const {
if (worklet_type == WorkletType::kBidder) {
return num_idle_processes_of_type + bidder_processes_.size() <
kMaxBidderProcesses;
}
return num_idle_processes_of_type + seller_processes_.size() <
kMaxSellerProcesses;
}
DedicatedAuctionProcessManager::DedicatedAuctionProcessManager(
TrustedSignalsCacheImpl* trusted_signals_cache)
: AuctionProcessManager(trusted_signals_cache) {}
DedicatedAuctionProcessManager::~DedicatedAuctionProcessManager() = default;
AuctionProcessManager::WorkletProcess::ProcessContext
DedicatedAuctionProcessManager::CreateProcessInternal(
WorkletProcess& worklet_process) {
mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> service;
content::ServiceProcessHost::Launch(
service.InitWithNewPipeAndPassReceiver(),
ServiceProcessHost::Options()
.WithDisplayName("Protected Audience JavaScript Process")
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40812055) add a utility helper for Jit.
.WithChildFlags(ChildProcessHost::CHILD_RENDERER)
#endif
.WithProcessCallback(
base::BindOnce(&WorkletProcess::OnLaunchedWithProcess,
worklet_process.weak_ptr_factory_.GetWeakPtr()))
.Pass());
return WorkletProcess::ProcessContext(std::move(service));
}
scoped_refptr<AuctionProcessManager::WorkletProcess>
DedicatedAuctionProcessManager::LaunchProcess(
WorkletType worklet_type,
const url::Origin& origin,
scoped_refptr<SiteInstance> site_instance,
bool is_idle) {
// Start all idle processes unbound and all non-idle processes bound.
scoped_refptr<WorkletProcess> worklet_process =
base::MakeRefCounted<WorkletProcess>(
this, /*site_instance=*/nullptr, worklet_type, origin,
/*uses_shared_process=*/false, /*is_idle=*/is_idle,
/*is_bound_to_origin=*/!is_idle);
worklet_process->SetService(CreateProcessInternal(*worklet_process));
return worklet_process;
}
scoped_refptr<SiteInstance>
DedicatedAuctionProcessManager::MaybeComputeSiteInstance(
SiteInstance* frame_site_instance,
const url::Origin& worklet_origin) {
return nullptr;
}
bool DedicatedAuctionProcessManager::TryUseSharedProcess(
ProcessHandle* process_handle) {
return false;
}
InRendererAuctionProcessManager::InRendererAuctionProcessManager(
TrustedSignalsCacheImpl* trusted_signals_cache)
: AuctionProcessManager(trusted_signals_cache) {}
InRendererAuctionProcessManager::~InRendererAuctionProcessManager() = default;
AuctionProcessManager::WorkletProcess::ProcessContext
InRendererAuctionProcessManager::CreateProcessInternal(
WorkletProcess& worklet_process) {
SiteInstance* site_instance = worklet_process.site_instance();
if (site_instance->GetBrowserContext()->ShutdownStarted()) {
// This browser context is shutting down, so we shouldn't start any
// processes, in part because managing their lifetime will be impossible.
// So... just give up. Create a pipe and drop the other end. The service
// pipe will be broken, but that should be OK since the destination of the
// async callback on process assignment should get deleted before we get
// back to the event loop.
return WorkletProcess::ProcessContext(
mojo::PendingReceiver<auction_worklet::mojom::AuctionWorkletService>()
.InitWithNewPipeAndPassRemote());
}
mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> service;
static_cast<SiteInstanceImpl*>(site_instance)
->GetOrCreateProcess(ProcessAllocationContext{
ProcessAllocationSource::kAuctionProcessManager})
->Init();
site_instance->GetProcess()->BindReceiver(
service.InitWithNewPipeAndPassReceiver());
return WorkletProcess::ProcessContext(std::move(service),
site_instance->GetProcess());
}
scoped_refptr<AuctionProcessManager::WorkletProcess>
InRendererAuctionProcessManager::LaunchProcess(
WorkletType worklet_type,
const url::Origin& origin,
scoped_refptr<SiteInstance> site_instance,
bool is_idle) {
DCHECK(site_instance);
DCHECK(site_instance->RequiresDedicatedProcess());
auto worklet_process = base::MakeRefCounted<WorkletProcess>(
this, std::move(site_instance), worklet_type, origin,
/*uses_shared_process=*/false, /*is_idle=*/is_idle,
/*is_bound_to_origin=*/true);
worklet_process->SetService(CreateProcessInternal(*worklet_process));
// This process must be bound to an origin because it's launched in
// the site instance.
return worklet_process;
}
scoped_refptr<SiteInstance>
InRendererAuctionProcessManager::MaybeComputeSiteInstance(
SiteInstance* frame_site_instance,
const url::Origin& worklet_origin) {
return frame_site_instance->GetRelatedSiteInstance(worklet_origin.GetURL());
}
bool InRendererAuctionProcessManager::TryUseSharedProcess(
ProcessHandle* process_handle) {
// If this needs a dedicated process due to site isolation, return and let
// AuctionProcessManager do the quota thing. Then it will ask for one in
// LaunchProcess once process count is low enough. This is only reasonable to
// do since dedicated processes are shared among different BrowsingInstances,
// so the stored `process_handle->site_instance_` requiring a dedicated
// process is as good as any.
if (process_handle->site_instance_->RequiresDedicatedProcess())
return false;
// Shared process case.
auto worklet_process = base::MakeRefCounted<WorkletProcess>(
this, process_handle->site_instance_, process_handle->worklet_type(),
process_handle->origin(), /*uses_shared_process=*/true, /*is_idle=*/false,
/*is_bound_to_origin=*/true);
worklet_process->SetService(CreateProcessInternal(*worklet_process));
process_handle->AssignProcess(std::move(worklet_process));
return true;
}
} // namespace content