Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/auction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { type Metrics, useMetrics } from './utils/perfMetrics.js';
import { adjustCpm } from './utils/cpm.js';
import { getGlobal } from './prebidGlobal.js';
import { ttlCollection } from './utils/ttlCollection.js';
import { getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js';
import { getEffectiveMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js';
import type { Bid, BidResponse } from "./bidfactory.ts";
import type { AdUnitCode, BidderCode, Identifier, ORTBFragments } from './types/common.d.ts';
import type { TargetingMap } from "./targeting.ts";
Expand Down Expand Up @@ -197,7 +197,10 @@ export function newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels,
let _bidderRequests: BidderRequest<BidderCode>[] = [];
const _bidsReceived = ttlCollection<Bid>({
startTime: (bid) => bid.responseTimestamp,
ttl: (bid) => getMinBidCacheTTL() == null ? null : Math.max(getMinBidCacheTTL(), bid.ttl) * 1000
ttl: (bid) => {
const minTTL = getEffectiveMinBidCacheTTL(bid);
return minTTL == null ? null : Math.max(minTTL, bid.ttl) * 1000;
}
});
let _noBids: BidRequest<BidderCode>[] = [];
let _winningBids: Bid[] = [];
Expand Down Expand Up @@ -434,6 +437,7 @@ export function newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels,

function setBidTargeting(bid) {
adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid);
_bidsReceived.refresh();
}

events.on(EVENTS.PBS_ANALYTICS, (event) => {
Expand Down
22 changes: 18 additions & 4 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { AuctionIndex } from './auctionIndex.js';
import { BID_STATUS, JSON_MAPPING } from './constants.js';
import { useMetrics } from './utils/perfMetrics.js';
import { ttlCollection } from './utils/ttlCollection.js';
import { getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js';
import { getEffectiveMinBidCacheTTL, getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js';

/**
* Creates new instance of auctionManager. There will only be one instance of auctionManager but
Expand All @@ -42,8 +42,19 @@ import { getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js';
export function newAuctionManager() {
const _auctions = ttlCollection({
startTime: (au) => au.end.then(() => au.getAuctionEnd()),
ttl: (au) => getMinBidCacheTTL() == null ? null : au.end.then(() => {
return Math.max(getMinBidCacheTTL(), ...au.getBidsReceived().map(bid => bid.ttl)) * 1000
ttl: (au) => au.end.then(() => {
const bids = au.getBidsReceived();
if (bids.length === 0) {
const minTTL = getMinBidCacheTTL();
return minTTL == null ? null : minTTL * 1000;
Comment on lines +45 to +49
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refresh auction TTL when targeting status is updated

The auction-level TTL is computed once inside au.end.then(...), so it snapshots each bid’s status at auction end; however bids are marked BID_TARGETING_SET later via setStatusForBids when setTargetingForGPT runs. Because _auctions.refresh() is never triggered on that status transition, auctions can still expire using the pre-targeting TTL (for example minBidCacheTTL/bid TTL) instead of minWinningBidCacheTTL, which reintroduces the lazy-load renderAd/"cannot find ad" failure this change is trying to prevent when all bids have short TTLs.

Useful? React with 👍 / 👎.

}
const ttls = bids.map(bid => {
const minTTL = getEffectiveMinBidCacheTTL(bid);
if (minTTL == null) return null;
return Math.max(minTTL, bid.ttl);
});
if (ttls.some(t => t == null)) return null;
return Math.max(...ttls) * 1000;
}),
});

Expand Down Expand Up @@ -131,7 +142,10 @@ export function newAuctionManager() {

if (bid && status === BID_STATUS.BID_TARGETING_SET) {
const auction = getAuction(bid.auctionId);
if (auction) auction.setBidTargeting(bid);
if (auction) {
auction.setBidTargeting(bid);
_auctions.refresh();
}
}
}

Expand Down
46 changes: 44 additions & 2 deletions src/bidTTL.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { config } from './config.js';
import { logError } from './utils.js';
import { BID_STATUS } from './constants.js';
const CACHE_TTL_SETTING = 'minBidCacheTTL';
const MIN_TARGETED_BID_CACHE_TTL_SETTING = 'minTargetedBidCacheTTL';
let TTL_BUFFER = 1;
let minCacheTTL = null;
let minTargetedBidCacheTTL = null;
const listeners = [];

declare module './config' {
Expand All @@ -21,6 +24,12 @@ declare module './config' {
* If unset (the default), bids are kept for the lifetime of the page.
*/
[CACHE_TTL_SETTING]?: number;
/**
* When set, overrides minBidCacheTTL for bids that have had targeting set (e.g. bids sent to the ad server).
* Useful with GPT lazy load when the scroll milestone for render may take a long time.
* If unset, minBidCacheTTL applies to all bids. Setting to Infinity keeps targeted bids indefinitely.
*/
[MIN_TARGETED_BID_CACHE_TTL_SETTING]?: number;
}
}

Expand All @@ -40,14 +49,47 @@ export function getMinBidCacheTTL() {
return minCacheTTL;
}

export function getMinTargetedBidCacheTTL() {
return minTargetedBidCacheTTL;
}

/**
* Returns the effective minimum cache TTL in seconds for a bid.
* When minTargetedBidCacheTTL is set and the bid has had targeting set, uses that;
* otherwise uses minBidCacheTTL. Returns null if no minimum applies (bid kept for page lifetime).
*/
export function getEffectiveMinBidCacheTTL(bid) {
const baseTTL = minCacheTTL;
if (baseTTL == null && minTargetedBidCacheTTL == null) {
return null;
}
if (bid?.status === BID_STATUS.BID_TARGETING_SET && typeof minTargetedBidCacheTTL === 'number') {
return minTargetedBidCacheTTL;
}
return baseTTL;
}

function notifyCacheTTLChange() {
listeners.forEach(l => l(minCacheTTL));
}

config.getConfig(CACHE_TTL_SETTING, (cfg) => {
const prev = minCacheTTL;
minCacheTTL = cfg?.[CACHE_TTL_SETTING];
minCacheTTL = typeof minCacheTTL === 'number' ? minCacheTTL : null;
if (prev !== minCacheTTL) {
listeners.forEach(l => l(minCacheTTL))
notifyCacheTTLChange();
}
})
});

config.getConfig(MIN_TARGETED_BID_CACHE_TTL_SETTING, (cfg) => {
const prev = minTargetedBidCacheTTL;
minTargetedBidCacheTTL = cfg?.[MIN_TARGETED_BID_CACHE_TTL_SETTING];
minTargetedBidCacheTTL = typeof minTargetedBidCacheTTL === 'number' ? minTargetedBidCacheTTL : null;
if (prev !== minTargetedBidCacheTTL) {
notifyCacheTTLChange();
}
});

export function onMinBidCacheTTLChange(listener) {
listeners.push(listener);
Expand Down
60 changes: 57 additions & 3 deletions test/spec/auctionmanager_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getPriceByGranularity,
addBidResponse, resetAuctionState, responsesReady, newAuction
} from 'src/auction.js';
import { EVENTS, TARGETING_KEYS, S2S } from 'src/constants.js';
import { BID_STATUS, EVENTS, TARGETING_KEYS, S2S } from 'src/constants.js';
import * as auctionModule from 'src/auction.js';
import { registerBidder } from 'src/adapters/bidderFactory.js';
import { createBid } from 'src/bidfactory.js';
Expand All @@ -28,7 +28,7 @@ import { setConfig as setCurrencyConfig } from '../../modules/currency.js'
import { REJECTION_REASON } from '../../src/constants.js';
import { setDocumentHidden } from './unit/utils/focusTimeout_spec.js';
import { sandbox } from 'sinon';
import { getMinBidCacheTTL, onMinBidCacheTTLChange } from '../../src/bidTTL.js';
import { getEffectiveMinBidCacheTTL, getMinBidCacheTTL, getMinTargetedBidCacheTTL, onMinBidCacheTTLChange } from '../../src/bidTTL.js';
import { getGlobal } from '../../src/prebidGlobal.js';

/**
Expand Down Expand Up @@ -870,6 +870,29 @@ describe('auctionmanager.js', function () {
})
})

describe('setConfig(minTargetedBidCacheTTL)', () => {
it('should update getMinTargetedBidCacheTTL', () => {
expect(getMinTargetedBidCacheTTL()).to.eql(null);
config.setConfig({ minTargetedBidCacheTTL: 3600 });
expect(getMinTargetedBidCacheTTL()).to.eql(3600);
});

it('getEffectiveMinBidCacheTTL uses minTargetedBidCacheTTL for bids with targeting set', () => {
config.setConfig({ minBidCacheTTL: 30, minTargetedBidCacheTTL: 3600 });
const bidWithTargeting = { status: BID_STATUS.BID_TARGETING_SET };
const bidWithoutTargeting = { status: 'other' };
expect(getEffectiveMinBidCacheTTL(bidWithTargeting)).to.eql(3600);
expect(getEffectiveMinBidCacheTTL(bidWithoutTargeting)).to.eql(30);
});

it('getEffectiveMinBidCacheTTL uses minBidCacheTTL when minTargetedBidCacheTTL not set', () => {
config.resetConfig();
config.setConfig({ minBidCacheTTL: 30 });
const bidWithTargeting = { status: BID_STATUS.BID_TARGETING_SET };
expect(getEffectiveMinBidCacheTTL(bidWithTargeting)).to.eql(30);
})
})

describe('minBidCacheTTL', () => {
let clock, auction;
beforeEach(() => {
Expand Down Expand Up @@ -910,14 +933,45 @@ describe('auctionmanager.js', function () {

it('pick up updates to minBidCacheTTL that happen during bid lifetime', async () => {
auction.callBids();
await auction.edn;
await auction.end;
clock.tick(10 * 1000);
config.setConfig({
minBidCacheTTL: 20
})
await clock.tick(0);
await clock.tick(20 * 1000);
expect(auctionManager.getBidsReceived().length).to.equal(1);
});

it('do not expire targeted bids when minTargetedBidCacheTTL is set', async () => {
config.setConfig({
minBidCacheTTL: 30,
minTargetedBidCacheTTL: 3600
});
bids = [
{
adUnitCode: ADUNIT_CODE,
adUnitId: ADUNIT_CODE,
ttl: 10,
adId: utils.getUniqueIdentifierStr(),
auctionId: auction.getAuctionId()
}, {
adUnitCode: ADUNIT_CODE,
adUnitId: ADUNIT_CODE,
ttl: 100,
adId: utils.getUniqueIdentifierStr(),
auctionId: auction.getAuctionId()
}
];
auction.callBids();
await auction.end;
const shortTtlBid = auctionManager.getBidsReceived().find(b => b.ttl === 10);
auctionManager.setStatusForBids(shortTtlBid.adId, BID_STATUS.BID_TARGETING_SET);
Comment thread
patmmccann marked this conversation as resolved.
await clock.tick(105 * 1000);
await clock.tick(0);
const bidsReceived = auctionManager.getBidsReceived();
expect(bidsReceived.length).to.equal(1);
expect(bidsReceived[0].adId).to.equal(shortTtlBid.adId);
})
})

Expand Down
Loading