0

PA: Auctions may start processes before loading IGs

Auctions are often stuck waiting for processes to be created.
Using the in-memory cache for interest group owners, we can anticipate
which worklet processes to start in parallel to interest group loading.

Bug: 365528726
Change-Id: Id6970baa964c4857ca544b7fb7a6fec6a1b3cf83
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5939361
Reviewed-by: Orr Bernstein <orrb@google.com>
Reviewed-by: mmenke <mmenke@chromium.org>
Commit-Queue: Abigail Katcoff <abigailkatcoff@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1370829}
This commit is contained in:
Abigail Katcoff
2024-10-18 21:13:13 +00:00
committed by Chromium LUCI CQ
parent 63dc9694d7
commit b76139ee60
8 changed files with 174 additions and 2 deletions

@ -960,6 +960,13 @@ void AuctionWorkletManager::RequestWorkletByKey(
std::move(worklet_available_callback), std::move(fatal_error_callback)));
}
void AuctionWorkletManager::MaybeStartAnticipatoryProcess(
const url::Origin& origin,
WorkletType worklet_type) {
auction_process_manager()->MaybeStartAnticipatoryProcess(
origin, delegate_->GetFrameSiteInstance().get(), worklet_type);
}
void AuctionWorkletManager::OnWorkletNoLongerUsable(WorkletOwner* worklet) {
DCHECK(worklets_.count(worklet->worklet_info()));
DCHECK_EQ(worklet, worklets_[worklet->worklet_info()]);

@ -326,6 +326,14 @@ class CONTENT_EXPORT AuctionWorkletManager {
size_t number_of_bidder_threads,
AuctionMetricsRecorder* auction_metrics_recorder);
// Start an anticipatory process for an origin if we have not yet
// done so and are able.
//
// Refer to AuctionProcessManager::MaybeStartAnticipatoryProcess
// for more details.
void MaybeStartAnticipatoryProcess(const url::Origin& origin,
WorkletType worklet_type);
private:
void OnWorkletNoLongerUsable(WorkletOwner* worklet);

@ -8,6 +8,7 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <list>
#include <map>
@ -2943,7 +2944,7 @@ void InterestGroupAuction::StartLoadInterestGroupsPhase(
weak_ptr_factory_.GetWeakPtr(), component_auction));
++num_pending_loads_;
}
bool try_starting_seller_worklet = false;
if (config_->non_shared_params.interest_group_buyers) {
for (const auto& buyer :
*config_->non_shared_params.interest_group_buyers) {
@ -2956,9 +2957,28 @@ void InterestGroupAuction::StartLoadInterestGroupsPhase(
base::BindOnce(&InterestGroupAuction::OnInterestGroupRead,
weak_ptr_factory_.GetWeakPtr()));
++num_pending_loads_;
std::optional<url::Origin> unused_signals_origin;
if (interest_group_manager_->GetCachedOwnerAndSignalsOrigins(
buyer, unused_signals_origin)) {
// Try to start a process on behalf of this buyer now -- we know this
// owner has groups in the database, so we'll likely need this process.
auction_worklet_manager_->MaybeStartAnticipatoryProcess(
buyer, AuctionWorkletManager::WorkletType::kBidder);
try_starting_seller_worklet = true;
}
}
}
// We want the top-level seller process to be started after the component
// seller processes to match the order that worklets are requested. Component
// seller processes are requested before the top-level seller processes to
// avoid deadlock, but anticipatory processes cannot cause deadlock because
// they can be cleared in order to respect process limits.
if (try_starting_seller_worklet) {
auction_worklet_manager_->MaybeStartAnticipatoryProcess(
config_->seller, AuctionWorkletManager::WorkletType::kSeller);
}
if (num_pending_loads_ == 0) {
// There is nothing to load. We move on to the bidding and scoring phase
// anyway, since it may need to wait for config promises to be resolved

@ -511,6 +511,9 @@ class CONTENT_EXPORT InterestGroupAuction
// completion. Passes it false if there are no interest groups that may
// participate in the auction (possibly because sellers aren't allowed to
// participate in the auction)
//
// Worklet processes may be created at this point for cached buyers, and for
// any seller whose auction has a cached buyer.
void StartLoadInterestGroupsPhase(
AuctionPhaseCompletionCallback load_interest_groups_phase_callback);

@ -44,6 +44,7 @@
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/test/values_test_util.h"
@ -79,6 +80,7 @@
#include "content/public/browser/tracing_controller.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
@ -24077,6 +24079,115 @@ IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionTraceTimeout) {
stop_run_loop.Run();
}
class UsesAnticipatoryProcessesTest : public InterestGroupBrowserTest {
public:
UsesAnticipatoryProcessesTest() {
feature_list_.InitAndEnableFeatureWithParameters(
features::kFledgeStartAnticipatoryProcesses,
{{"AnticipatoryProcessHoldTime", "10s"}});
// Set up the conditions so that Android will have desktop-like behavior for
// process creation.
scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kSitePerProcess);
}
private:
base::test::ScopedFeatureList feature_list_;
base::test::ScopedCommandLine scoped_command_line_;
};
IN_PROC_BROWSER_TEST_F(UsesAnticipatoryProcessesTest,
UsesAnticipatoryProcesses) {
GURL joining_url = embedded_https_test_server().GetURL("c.test", "/echo");
url::Origin joining_origin = url::Origin::Create(joining_url);
ASSERT_TRUE(NavigateToURL(shell(), joining_url));
// Join an interest group. The owner, a.test, will be in the in-memory
// cache.
GURL ad_url =
embedded_https_test_server().GetURL("a.test", "/echo?render_winner");
// Use a bidding script that does not bid to prevent the auction from having a
// winner. If the auction has a winner, there will be a race condition where a
// a worklet will be created for reporting.
GURL script_url = embedded_https_test_server().GetURL(
"a.test", "/interest_group/bidding_logic_do_not_bid.js");
blink::InterestGroup interest_group =
blink::TestInterestGroupBuilder(
/*owner=*/url::Origin::Create(script_url),
/*name=*/"interest_group")
.SetBiddingUrl(script_url)
.SetAds({{{ad_url, std::nullopt}}})
.Build();
AttachInterestGroupObserver();
manager_->JoinInterestGroup(interest_group, joining_url);
WaitForAccessObserved({{"global", TestInterestGroupObserver::kJoin,
interest_group.owner, "interest_group"}});
std::optional<url::Origin> cached_signals_origin;
EXPECT_TRUE(manager_->GetCachedOwnerAndSignalsOrigins(interest_group.owner,
cached_signals_origin));
const char kConfigTemplate[] = R"({
seller: $1,
decisionLogicURL: $2,
auctionSignals: "bidderAllowsComponentAuction,"+
"sellerAllowsComponentAuction",
componentAuctions:
[{
seller: $3,
decisionLogicURL: $4,
interestGroupBuyers: [$5, $6],
auctionSignals: "bidderAllowsComponentAuction,"+
"sellerAllowsComponentAuction"
}]
})";
GURL top_level_seller_script = embedded_https_test_server().GetURL(
"c.test", "/interest_group/decision_logic.js");
GURL component_seller_script = embedded_https_test_server().GetURL(
"d.test", "/interest_group/decision_logic.js");
content_browser_client_->AddToAllowList(
{url::Origin::Create(component_seller_script)});
std::string auction_config = JsReplace(
kConfigTemplate, url::Origin::Create(top_level_seller_script),
top_level_seller_script, url::Origin::Create(component_seller_script),
component_seller_script, interest_group.owner,
url::Origin::Create(embedded_https_test_server().GetURL("b.test", "/")));
// Run the auction.
//
// 1. a.test, the first buyer, is cached. That means we should start an
// anticipatory process for it.
// 2. b.test, the second buyer, doesn't have any IGs. We won't request an
// anticipatory process or create a worklet for it.
// 3. c.test, the top-level seller, will not have an anticipatory process
// started for it, because there are no top-level buyers cached (component
// auctions actually don't allow top-level buyers). A worklet
// will still be created for it.
// 4. d.test, the component seller, will have an anticipatory process
// started for it because there's a cached buyer in its auction (a.test).
//
// Overall there will be 2 anticipatory processes started and 3 worklets
// requested.
base::HistogramTester histogram_tester;
auto result = RunAuctionAndWait(auction_config);
histogram_tester.ExpectUniqueSample("Ads.InterestGroup.Auction.Result",
AuctionResult::kNoBids, 1);
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.Seller.RequestWorkletServiceOutcome", 2);
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.Seller.RequestWorkletServiceOutcome",
AuctionProcessManager::RequestWorkletServiceOutcome::kUsedIdleProcess, 1);
histogram_tester.ExpectBucketCount(
"Ads.InterestGroup.Auction.Seller.RequestWorkletServiceOutcome",
AuctionProcessManager::RequestWorkletServiceOutcome::
kCreatedNewDedicatedProcess,
1);
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.Buyer.RequestWorkletServiceOutcome",
AuctionProcessManager::RequestWorkletServiceOutcome::kUsedIdleProcess, 1);
}
class AuctionConfigReportingTimeoutEnabledTest
: public InterestGroupBrowserTest {
public:

@ -649,7 +649,9 @@ void InterestGroupCachingStorage::StartTimerForInterestGroupHold(
void InterestGroupCachingStorage::UpdateCachedOriginsIfEnabled(
const url::Origin& owner,
const std::vector<StorageInterestGroup>& interest_groups) {
if (!base::FeatureList::IsEnabled(features::kFledgeUsePreconnectCache)) {
if (!base::FeatureList::IsEnabled(features::kFledgeUsePreconnectCache) &&
!base::FeatureList::IsEnabled(
features::kFledgeStartAnticipatoryProcesses)) {
return;
}
if (interest_groups.empty()) {

@ -18609,6 +18609,26 @@
]
}
],
"ProtectedAudienceEarlyProcessCreationStudy": [
{
"platforms": [
"android",
"chromeos",
"chromeos_lacros",
"linux",
"mac",
"windows"
],
"experiments": [
{
"name": "Enabled",
"enable_features": [
"FledgeStartAnticipatoryProcesses"
]
}
]
}
],
"ProtectedAudienceMorePAggMetrics": [
{
"platforms": [

@ -118,6 +118,7 @@ chromium-metrics-reviews@google.com.
<int value="1" label="Used shared process"/>
<int value="2" label="Used existing dedicated process"/>
<int value="3" label="Created new dedicated process"/>
<int value="4" label="Used idle process"/>
</enum>
<enum name="SalientImageUrlFetchResult">