0

Protected Audience: prepare contexts only if we received promises

We're currently preparing more bidder contexts than we need. Aborted
auctions are likely a major cause of unused contexts. Only prepare
contexts if we've received promises (using promises as an indicator that
an auction is likely to proceed).

Bug: 405175230
Change-Id: I5a0c64d39cfe90029ea4384cd16e4ed025b24baa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6377184
Reviewed-by: Russ Hamilton <behamilton@google.com>
Commit-Queue: Abigail Katcoff <abigailkatcoff@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1436100}
This commit is contained in:
Abigail Katcoff
2025-03-21 09:36:24 -07:00
committed by Chromium LUCI CQ
parent e1ba85a165
commit d161f23cf4
5 changed files with 147 additions and 0 deletions

@ -788,6 +788,12 @@ void BidderWorklet::FinishGenerateBid(
task->finalize_generate_bid_receiver_id = std::nullopt;
task->wait_promises = base::TimeTicks::Now() - task->trace_wait_deps_start;
GenerateBidIfReady(task);
if (!finalized_any_bid_) {
finalized_any_bid_ = true;
if (features::kFledgeWaitForPromisesToPrepareContexts.Get()) {
MaybePrepareContexts();
}
}
}
BidderWorklet::GenerateBidTask::GenerateBidTask() = default;
@ -2499,6 +2505,8 @@ void BidderWorklet::MaybePrepareContexts() {
if (!base::FeatureList::IsEnabled(
features::kFledgePrepareBidderContextsInAdvance) ||
generate_bid_tasks_.empty() || !IsCodeReady() ||
(!finalized_any_bid_ &&
features::kFledgeWaitForPromisesToPrepareContexts.Get()) ||
base::FeatureList::IsEnabled(features::kFledgeAlwaysReuseBidderContext)) {
return;
}

@ -947,6 +947,11 @@ class CONTENT_EXPORT BidderWorklet : public mojom::BidderWorklet,
GenerateBidTaskList::iterator>
finalize_receiver_set_;
// Whether any bid was finalized. We use this as a heuristic to indicate that
// at least one auction is going to proceed without being aborted. In
// particular, we use it to determine whether we should prepare contexts.
bool finalized_any_bid_ = false;
ClosePipeCallback close_pipe_callback_;
// Errors that occurred while loading the code, if any.

@ -884,6 +884,22 @@ class BidderWorkletTest : public testing::Test {
direct_from_seller_auction_signals_header_ad_slot_);
}
void FinishGenerateBid(
mojo::AssociatedRemote<auction_worklet::mojom::GenerateBidFinalizer>&
bid_finalizer) {
bid_finalizer->FinishGenerateBid(
auction_signals_, per_buyer_signals_, per_buyer_timeout_,
per_buyer_currency_,
provide_direct_from_seller_signals_late_
? direct_from_seller_per_buyer_signals_
: std::nullopt,
direct_from_seller_per_buyer_signals_header_ad_slot_,
provide_direct_from_seller_signals_late_
? direct_from_seller_auction_signals_
: std::nullopt,
direct_from_seller_auction_signals_header_ad_slot_);
}
// Calls BeginGenerateBid()/FinishGenerateBid(), expecting the
// GenerateBidClient's OnGenerateBidComplete() method never to be invoked.
void GenerateBidExpectingNeverCompletes(
@ -9138,6 +9154,116 @@ TEST_P(BidderWorkletMultiThreadingTest,
"Ads.InterestGroup.Auction.PremadeContextsScheduledPerThread", 0);
}
TEST_P(BidderWorkletMultiThreadingTest,
PreparesContextsIfFinishGenerateBidBeforeOrAfterJavascriptDownload) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kFledgePrepareBidderContextsInAdvance,
{{"WaitForPromisesToPrepareContexts", "true"}});
interest_group_trusted_bidding_signals_url_ = GURL("https://signals.test/");
const GURL kFullSignalsUrl(
"https://signals.test/?hostname=top.window.test&interestGroupNames=Fred");
const char kJson[] = R"({"perInterestGroupData":
{"Fred": {"priorityVector": {"foo": 1.0}}}
})";
for (bool finalize_before_js_download : {true, false}) {
SCOPED_TRACE(finalize_before_js_download);
url_loader_factory_.ClearResponses();
auto bidder_worklet = CreateWorklet();
base::HistogramTester histogram_tester;
generate_bid_run_loop_ = std::make_unique<base::RunLoop>();
mojo::AssociatedRemote<auction_worklet::mojom::GenerateBidFinalizer>
bid_finalizer;
BeginGenerateBid(bidder_worklet.get(),
bid_finalizer.BindNewEndpointAndPassReceiver());
if (finalize_before_js_download) {
FinishGenerateBid(bid_finalizer);
task_environment_.RunUntilIdle();
AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
CreateBasicGenerateBidScript());
} else {
AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
CreateBasicGenerateBidScript());
task_environment_.RunUntilIdle();
FinishGenerateBid(bid_finalizer);
}
task_environment_.RunUntilIdle();
EXPECT_FALSE(generate_bid_run_loop_->AnyQuitCalled());
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.UsedPremadeContext", 0);
AddBidderJsonResponse(&url_loader_factory_, kFullSignalsUrl, kJson);
task_environment_.RunUntilIdle();
generate_bid_run_loop_->Run();
generate_bid_run_loop_.reset();
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.UsedPremadeContext", true, 1);
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.PremadeContextsScheduledPerThread", 1);
}
}
TEST_P(BidderWorkletMultiThreadingTest,
DoesNotPrepareContextsBeforeFinishGenerateBidIfWaitForPromisesEnabled) {
interest_group_trusted_bidding_signals_url_ = GURL("https://signals.test/");
const GURL kFullSignalsUrl(
"https://signals.test/?hostname=top.window.test&interestGroupNames=Fred");
const char kJson[] = R"({"perInterestGroupData":
{"Fred": {"priorityVector": {"foo": 1.0}}}
})";
for (bool wait_for_promises : {true, false}) {
SCOPED_TRACE(wait_for_promises);
url_loader_factory_.ClearResponses();
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kFledgePrepareBidderContextsInAdvance,
{{"WaitForPromisesToPrepareContexts",
wait_for_promises ? "true" : "false"}});
auto bidder_worklet = CreateWorklet();
AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
CreateBasicGenerateBidScript());
base::HistogramTester histogram_tester;
generate_bid_run_loop_ = std::make_unique<base::RunLoop>();
mojo::AssociatedRemote<auction_worklet::mojom::GenerateBidFinalizer>
bid_finalizer;
BeginGenerateBid(bidder_worklet.get(),
bid_finalizer.BindNewEndpointAndPassReceiver());
task_environment_.RunUntilIdle();
EXPECT_FALSE(generate_bid_run_loop_->AnyQuitCalled());
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.UsedPremadeContext", 0);
AddBidderJsonResponse(&url_loader_factory_, kFullSignalsUrl, kJson);
task_environment_.RunUntilIdle();
// FinishGenerateBid should cause us to generate our bid, but because this
// is coming after signals, we never got a chance to prepare contexts.
FinishGenerateBid(bid_finalizer);
generate_bid_run_loop_->Run();
generate_bid_run_loop_.reset();
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.UsedPremadeContext", !wait_for_promises, 1);
histogram_tester.ExpectTotalCount(
"Ads.InterestGroup.Auction.PremadeContextsScheduledPerThread",
!wait_for_promises);
}
}
TEST_P(BidderWorkletMultiThreadingTest,
DoesNotMakeMorePremadeContextsAfterFirstGenerateBid) {
base::test::ScopedFeatureList scoped_feature_list;

@ -61,6 +61,11 @@ BASE_FEATURE_PARAM(int,
&kFledgePrepareBidderContextsInAdvance,
"BidderContextsMultiplier",
1);
BASE_FEATURE_PARAM(bool,
kFledgeWaitForPromisesToPrepareContexts,
&kFledgePrepareBidderContextsInAdvance,
"WaitForPromisesToPrepareContexts",
false);
BASE_FEATURE(kFledgeBidderUseBalancingThreadSelector,
"FledgeBidderUseBalancingThreadSelector",

@ -45,6 +45,9 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(
kFledgeMinBidderContextsPerThreadInAdvance);
CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(int, kFledgeBidderContextsDivisor);
CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(int, kFledgeBidderContextsMultiplier);
CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(
bool,
kFledgeWaitForPromisesToPrepareContexts);
// Instead of using a hash to assign group-by-origin IGs to threads, use
// a round robin on joining-origin while ensuring a maximum allowed imbalance