From c06c4650ea3f8edcaa0f88ecfa42cfc60e6414ea Mon Sep 17 00:00:00 2001 From: Harrrdik18 Date: Sat, 28 Feb 2026 18:29:12 +0530 Subject: [PATCH 01/10] Refactor bid TTL handling to introduce effective minimum TTL logic - Updated auction and auctionManager to utilize getEffectiveMinBidCacheTTL for determining bid TTL based on targeting status. - Added minWinningBidCacheTTL configuration to allow for differentiated TTL handling for winning bids. - Enhanced bidTTL module with new functions to support effective TTL calculations. - Updated tests to cover new TTL logic and configurations, ensuring correct behavior for bids with and without targeting set. --- src/auction.ts | 8 +++-- src/auctionManager.js | 17 ++++++++-- src/bidTTL.ts | 46 ++++++++++++++++++++++++-- test/spec/auctionmanager_spec.js | 57 ++++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 10 deletions(-) diff --git a/src/auction.ts b/src/auction.ts index a6912be69be..84c3bf7de01 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -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"; @@ -189,7 +189,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _bidderRequests: BidderRequest[] = []; const _bidsReceived = ttlCollection({ 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[] = []; let _winningBids: Bid[] = []; @@ -426,6 +429,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a function setBidTargeting(bid) { adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); + _bidsReceived.refresh(); } events.on(EVENTS.SEAT_NON_BID, (event) => { diff --git a/src/auctionManager.js b/src/auctionManager.js index 0e0d8af8f9c..29c5f9e419d 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -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 @@ -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; + } + 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; }), }); diff --git a/src/bidTTL.ts b/src/bidTTL.ts index c6e5db37eb8..1fba4217cf4 100644 --- a/src/bidTTL.ts +++ b/src/bidTTL.ts @@ -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_WINNING_BID_CACHE_TTL_SETTING = 'minWinningBidCacheTTL'; let TTL_BUFFER = 1; let minCacheTTL = null; +let minWinningBidCacheTTL = null; const listeners = []; declare module './config' { @@ -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. winning 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 winning bids indefinitely. + */ + [MIN_WINNING_BID_CACHE_TTL_SETTING]?: number; } } @@ -40,14 +49,47 @@ export function getMinBidCacheTTL() { return minCacheTTL; } +export function getMinWinningBidCacheTTL() { + return minWinningBidCacheTTL; +} + +/** + * Returns the effective minimum cache TTL in seconds for a bid. + * When minWinningBidCacheTTL 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 && minWinningBidCacheTTL == null) { + return null; + } + if (bid?.status === BID_STATUS.BID_TARGETING_SET && typeof minWinningBidCacheTTL === 'number') { + return minWinningBidCacheTTL; + } + 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_WINNING_BID_CACHE_TTL_SETTING, (cfg) => { + const prev = minWinningBidCacheTTL; + minWinningBidCacheTTL = cfg?.[MIN_WINNING_BID_CACHE_TTL_SETTING]; + minWinningBidCacheTTL = typeof minWinningBidCacheTTL === 'number' ? minWinningBidCacheTTL : null; + if (prev !== minWinningBidCacheTTL) { + notifyCacheTTLChange(); + } +}); export function onMinBidCacheTTLChange(listener) { listeners.push(listener); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 6ebb8666096..63a9a56b7b4 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -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'; @@ -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, getMinWinningBidCacheTTL, onMinBidCacheTTLChange} from '../../src/bidTTL.js'; import {getGlobal} from '../../src/prebidGlobal.js'; /** @@ -866,6 +866,29 @@ describe('auctionmanager.js', function () { }) }) + describe('setConfig(minWinningBidCacheTTL)', () => { + it('should update getMinWinningBidCacheTTL', () => { + expect(getMinWinningBidCacheTTL()).to.eql(null); + config.setConfig({minWinningBidCacheTTL: 3600}); + expect(getMinWinningBidCacheTTL()).to.eql(3600); + }); + + it('getEffectiveMinBidCacheTTL uses minWinningBidCacheTTL for bids with targeting set', () => { + config.setConfig({minBidCacheTTL: 30, minWinningBidCacheTTL: 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 minWinningBidCacheTTL 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(() => { @@ -906,7 +929,7 @@ 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 @@ -914,6 +937,34 @@ describe('auctionmanager.js', function () { await clock.tick(0); await clock.tick(20 * 1000); expect(auctionManager.getBidsReceived().length).to.equal(1); + }); + + it('do not expire winning bids with targeting set when minWinningBidCacheTTL is set', async () => { + config.setConfig({ + minBidCacheTTL: 30, + minWinningBidCacheTTL: 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); + await clock.tick(35 * 1000); + expect(auctionManager.getBidsReceived().length).to.equal(2); }) }) From 7abdc1c158ed9a02281a6daf449c9b46c992b02a Mon Sep 17 00:00:00 2001 From: vasujain00 Date: Fri, 27 Mar 2026 17:08:48 +0530 Subject: [PATCH 02/10] Update bid TTL handling to use minTargetedBidCacheTTL - Refactored bid TTL logic to replace minWinningBidCacheTTL with minTargetedBidCacheTTL for better clarity and functionality. - Updated related functions and configurations to reflect the new naming and ensure correct behavior for bids with targeting set. - Enhanced tests to validate the new TTL handling for targeted bids, ensuring accurate cache expiration logic. --- src/auctionManager.js | 5 ++++- src/bidTTL.ts | 32 ++++++++++++++++---------------- test/spec/auctionmanager_spec.js | 29 ++++++++++++++++------------- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/auctionManager.js b/src/auctionManager.js index 29c5f9e419d..dce65b2111c 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -142,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(); + } } } diff --git a/src/bidTTL.ts b/src/bidTTL.ts index 1fba4217cf4..adbdf810b31 100644 --- a/src/bidTTL.ts +++ b/src/bidTTL.ts @@ -2,10 +2,10 @@ import {config} from './config.js'; import {logError} from './utils.js'; import {BID_STATUS} from './constants.js'; const CACHE_TTL_SETTING = 'minBidCacheTTL'; -const MIN_WINNING_BID_CACHE_TTL_SETTING = 'minWinningBidCacheTTL'; +const MIN_TARGETED_BID_CACHE_TTL_SETTING = 'minTargetedBidCacheTTL'; let TTL_BUFFER = 1; let minCacheTTL = null; -let minWinningBidCacheTTL = null; +let minTargetedBidCacheTTL = null; const listeners = []; declare module './config' { @@ -25,11 +25,11 @@ declare module './config' { */ [CACHE_TTL_SETTING]?: number; /** - * When set, overrides minBidCacheTTL for bids that have had targeting set (e.g. winning bids sent to the ad server). + * 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 winning bids indefinitely. + * If unset, minBidCacheTTL applies to all bids. Setting to Infinity keeps targeted bids indefinitely. */ - [MIN_WINNING_BID_CACHE_TTL_SETTING]?: number; + [MIN_TARGETED_BID_CACHE_TTL_SETTING]?: number; } } @@ -49,22 +49,22 @@ export function getMinBidCacheTTL() { return minCacheTTL; } -export function getMinWinningBidCacheTTL() { - return minWinningBidCacheTTL; +export function getMinTargetedBidCacheTTL() { + return minTargetedBidCacheTTL; } /** * Returns the effective minimum cache TTL in seconds for a bid. - * When minWinningBidCacheTTL is set and the bid has had targeting set, uses that; + * 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 && minWinningBidCacheTTL == null) { + if (baseTTL == null && minTargetedBidCacheTTL == null) { return null; } - if (bid?.status === BID_STATUS.BID_TARGETING_SET && typeof minWinningBidCacheTTL === 'number') { - return minWinningBidCacheTTL; + if (bid?.status === BID_STATUS.BID_TARGETING_SET && typeof minTargetedBidCacheTTL === 'number') { + return minTargetedBidCacheTTL; } return baseTTL; } @@ -82,11 +82,11 @@ config.getConfig(CACHE_TTL_SETTING, (cfg) => { } }); -config.getConfig(MIN_WINNING_BID_CACHE_TTL_SETTING, (cfg) => { - const prev = minWinningBidCacheTTL; - minWinningBidCacheTTL = cfg?.[MIN_WINNING_BID_CACHE_TTL_SETTING]; - minWinningBidCacheTTL = typeof minWinningBidCacheTTL === 'number' ? minWinningBidCacheTTL : null; - if (prev !== minWinningBidCacheTTL) { +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(); } }); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 63a9a56b7b4..7a581bec230 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -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 {getEffectiveMinBidCacheTTL, getMinBidCacheTTL, getMinWinningBidCacheTTL, onMinBidCacheTTLChange} from '../../src/bidTTL.js'; +import {getEffectiveMinBidCacheTTL, getMinBidCacheTTL, getMinTargetedBidCacheTTL, onMinBidCacheTTLChange} from '../../src/bidTTL.js'; import {getGlobal} from '../../src/prebidGlobal.js'; /** @@ -866,22 +866,22 @@ describe('auctionmanager.js', function () { }) }) - describe('setConfig(minWinningBidCacheTTL)', () => { - it('should update getMinWinningBidCacheTTL', () => { - expect(getMinWinningBidCacheTTL()).to.eql(null); - config.setConfig({minWinningBidCacheTTL: 3600}); - expect(getMinWinningBidCacheTTL()).to.eql(3600); + describe('setConfig(minTargetedBidCacheTTL)', () => { + it('should update getMinTargetedBidCacheTTL', () => { + expect(getMinTargetedBidCacheTTL()).to.eql(null); + config.setConfig({minTargetedBidCacheTTL: 3600}); + expect(getMinTargetedBidCacheTTL()).to.eql(3600); }); - it('getEffectiveMinBidCacheTTL uses minWinningBidCacheTTL for bids with targeting set', () => { - config.setConfig({minBidCacheTTL: 30, minWinningBidCacheTTL: 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 minWinningBidCacheTTL not set', () => { + it('getEffectiveMinBidCacheTTL uses minBidCacheTTL when minTargetedBidCacheTTL not set', () => { config.resetConfig(); config.setConfig({minBidCacheTTL: 30}); const bidWithTargeting = {status: BID_STATUS.BID_TARGETING_SET}; @@ -939,10 +939,10 @@ describe('auctionmanager.js', function () { expect(auctionManager.getBidsReceived().length).to.equal(1); }); - it('do not expire winning bids with targeting set when minWinningBidCacheTTL is set', async () => { + it('do not expire targeted bids when minTargetedBidCacheTTL is set', async () => { config.setConfig({ minBidCacheTTL: 30, - minWinningBidCacheTTL: 3600 + minTargetedBidCacheTTL: 3600 }); bids = [ { @@ -963,8 +963,11 @@ describe('auctionmanager.js', function () { await auction.end; const shortTtlBid = auctionManager.getBidsReceived().find(b => b.ttl === 10); auctionManager.setStatusForBids(shortTtlBid.adId, BID_STATUS.BID_TARGETING_SET); - await clock.tick(35 * 1000); - expect(auctionManager.getBidsReceived().length).to.equal(2); + 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); }) }) From 87a026d39cbf34b1f283b57e3ec3e06b6ec642d1 Mon Sep 17 00:00:00 2001 From: vasujain00 Date: Sat, 28 Mar 2026 01:31:26 +0530 Subject: [PATCH 03/10] chore: remove unnecessary documentation drafts Made-with: Cursor --- DOCS_PR.md | 48 ----------------------------------------------- PR_DESCRIPTION.md | 41 ---------------------------------------- 2 files changed, 89 deletions(-) delete mode 100644 DOCS_PR.md delete mode 100644 PR_DESCRIPTION.md diff --git a/DOCS_PR.md b/DOCS_PR.md deleted file mode 100644 index bfca31b4735..00000000000 --- a/DOCS_PR.md +++ /dev/null @@ -1,48 +0,0 @@ -# Docs PR: minTargetedBidCacheTTL - -**Target:** Prebid publisher docs – add to the [setConfig](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html) page, after the existing **Minimum bid cache TTL** section. - ---- - -## Section to add (after "Minimum bid cache TTL") - -### Minimum cache TTL for targeted bids - -When using `minBidCacheTTL` to limit how long bids stay in memory, bids that have already been sent to the ad server (targeting set) can expire before the ad is rendered. That often happens with GPT lazy load or other delayed render: the ad is requested and targeting is set, but the slot only renders when the user scrolls. If the bid is dropped from cache before render, you get "cannot find ad" or similar errors. - -Use **`minTargetedBidCacheTTL`** to give those bids a longer (or unlimited) cache time than other bids: - -```javascript -pbjs.setConfig({ - minBidCacheTTL: 30, // drop non-targeted bids after 30s - minTargetedBidCacheTTL: Infinity // keep targeted bids until page unload (lazy-load / long-delay render) -}); -``` - -- When set, it overrides `minBidCacheTTL` only for bids that have had **targeting set** (e.g. sent to GPT via `setTargetingForGPT`). -- When unset, all bids use `minBidCacheTTL` (current behavior). -- Use a number (seconds) for a longer but finite TTL, or `Infinity` to keep targeted bids for the life of the page. - -**Publisher choices when using bid cache TTL** - -If you use `minBidCacheTTL` (with or without `minTargetedBidCacheTTL`), you are making a tradeoff between memory and ad availability. Be explicit about what should happen when: - -1. **A bid expires after targeting but before render** - - Rely on `minTargetedBidCacheTTL` so targeted bids stay in cache until render, or - - Accept that the slot may show no ad / blank, or - - Run a new auction when the slot is about to render (e.g. in a lazy-load callback). - -2. **Bids are dropped for memory saving** - - Decide whether you prefer lower memory (shorter TTL) or fewer "missing ad" cases (longer TTL or `minTargetedBidCacheTTL`). - -**SSP / revenue note** - -Bids have a TTL from the bidder/SSP. If an ad is rendered **after** that TTL, the SSP may treat the bid as expired and not attribute revenue. Keeping bids in Prebid's cache longer (e.g. with `minTargetedBidCacheTTL`) does not change the SSP's own TTL. Use this setting when the delay is on your side (e.g. lazy load), not to extend the SSP's idea of when the bid is valid. - ---- - -## Link from Prebid.js PR - -In your Prebid.js PR description, add: - -**Docs:** [PR link to prebid.github.io or docs repo once opened] diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md deleted file mode 100644 index ec10a3fb0f0..00000000000 --- a/PR_DESCRIPTION.md +++ /dev/null @@ -1,41 +0,0 @@ -## Summary - -Adds `minTargetedBidCacheTTL` config option to prevent bids that have had targeting set from expiring, fixing "cannot find ad" / expired render errors when using GPT lazy load with scroll-based render. - -## Problem - -Setting `minBidCacheTTL` causes "cannot find ad" errors in lazy-load scenarios. The ad is fetched from GPT, but the scroll milestone for render takes a long time. Winning bids expire and are dropped from memory before GPT can render them. - -## Solution - -- **`minTargetedBidCacheTTL`** – When set, overrides `minBidCacheTTL` for bids that have had targeting set (bids sent to the ad server). -- If unset, behavior is unchanged (all bids use `minBidCacheTTL`). -- Set to `Infinity` (or a large value) to effectively never expire winning bids. - -## Usage - -```javascript -pbjs.setConfig({ - minBidCacheTTL: 30, - minTargetedBidCacheTTL: Infinity // keep targeted bids indefinitely for lazy-load GPT -}); -``` - -## Changes - -- **src/bidTTL.ts** – Added `minTargetedBidCacheTTL` config, `getMinTargetedBidCacheTTL()`, and `getEffectiveMinBidCacheTTL(bid)` -- **src/auction.ts** – Use effective TTL per bid; refresh bid TTL when targeting is set -- **src/auctionManager.js** – Use effective TTL when calculating auction TTL - -## Testing - -- `gulp lint` – passed -- `gulp test --file test/spec/auctionmanager_spec.js` – 91 tests passed - -## Labels - -`feature` | `minor` - ---- - -Fixes #12987 From ebad372588d47b26d7497c0c0130de47303ba68e Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 30 Mar 2026 13:22:52 -0400 Subject: [PATCH 04/10] Reformat import statements in auction.ts --- src/auction.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/auction.ts b/src/auction.ts index 3a39b99f3c9..bb84002cdaa 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -22,20 +22,20 @@ import { AUDIO, VIDEO } from './mediaTypes.js'; import { auctionManager } from './auctionManager.js'; import { bidderSettings } from './bidderSettings.js'; import * as events from './events.js'; -import adapterManager, {activityParams, type BidderRequest, type BidRequest} from './adapterManager.js'; -import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; -import {defer, PbPromise} from './utils/promise.js'; -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 {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"; -import type {AdUnit} from "./adUnits.ts"; -import type {MediaType} from "./mediaTypes.ts"; -import type {VideoContext} from "./video.ts"; +import adapterManager, { activityParams, type BidderRequest, type BidRequest } from './adapterManager.js'; +import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS } from './constants.js'; +import { defer, PbPromise } from './utils/promise.js'; +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 { 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"; +import type { AdUnit } from "./adUnits.ts"; +import type { MediaType } from "./mediaTypes.ts"; +import type { VideoContext } from "./video.ts"; import { isActivityAllowed } from './activities/rules.js'; import { ACTIVITY_ADD_BID_RESPONSE } from './activities/activities.js'; import { MODULE_TYPE_BIDDER } from './activities/modules.ts'; From 5ab49fb9680f2f959c20bfefec5ca05015892783 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 30 Mar 2026 13:23:40 -0400 Subject: [PATCH 05/10] Update auctionManager.js --- src/auctionManager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auctionManager.js b/src/auctionManager.js index f30d3445d5f..9251829497c 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -29,9 +29,9 @@ import { uniques, logWarn } from './utils.js'; import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js'; 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 {getEffectiveMinBidCacheTTL, getMinBidCacheTTL, onMinBidCacheTTLChange} from './bidTTL.js'; +import { useMetrics } from './utils/perfMetrics.js'; +import { ttlCollection } from './utils/ttlCollection.js'; +import { getEffectiveMinBidCacheTTL, getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js'; /** * Creates new instance of auctionManager. There will only be one instance of auctionManager but From 675a9e1e85c3db8109a3d3fff3ec187772820b99 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 31 Mar 2026 07:51:38 -0400 Subject: [PATCH 06/10] Import AdUnitDefinition type in auction.ts --- src/auction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auction.ts b/src/auction.ts index bb84002cdaa..d5d6d67032b 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -33,7 +33,7 @@ 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"; -import type { AdUnit } from "./adUnits.ts"; +import type { AdUnit, AdUnitDefinition } from "./adUnits.ts"; import type { MediaType } from "./mediaTypes.ts"; import type { VideoContext } from "./video.ts"; import { isActivityAllowed } from './activities/rules.js'; From ba8db67fc822e5947663b6fbce1ba92f5a68c625 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 2 Apr 2026 10:58:00 -0400 Subject: [PATCH 07/10] Update bidTTL.ts --- src/bidTTL.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bidTTL.ts b/src/bidTTL.ts index adbdf810b31..e56f123a033 100644 --- a/src/bidTTL.ts +++ b/src/bidTTL.ts @@ -1,6 +1,6 @@ -import {config} from './config.js'; -import {logError} from './utils.js'; -import {BID_STATUS} from './constants.js'; +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; From fe10d33beeafa1ec25ea71437540f065c55bc9d8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 2 Apr 2026 10:58:36 -0400 Subject: [PATCH 08/10] Fix import formatting in auctionmanager_spec.js --- test/spec/auctionmanager_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index f6001bfd8f2..4924bb79a89 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -27,9 +27,9 @@ 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 {getEffectiveMinBidCacheTTL, getMinBidCacheTTL, getMinTargetedBidCacheTTL, onMinBidCacheTTLChange} from '../../src/bidTTL.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; +import { sandbox } from 'sinon'; +import { getEffectiveMinBidCacheTTL, getMinBidCacheTTL, getMinTargetedBidCacheTTL, onMinBidCacheTTLChange } from '../../src/bidTTL.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest From c52686970ceb691a40567ad9f3f91ffe65358031 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 2 Apr 2026 12:02:25 -0400 Subject: [PATCH 09/10] Update auctionmanager_spec.js --- test/spec/auctionmanager_spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 4924bb79a89..94d762cc239 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -878,17 +878,17 @@ describe('auctionmanager.js', function () { }); 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'}; + 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}; + config.setConfig({ minBidCacheTTL: 30 }); + const bidWithTargeting = { status: BID_STATUS.BID_TARGETING_SET }; expect(getEffectiveMinBidCacheTTL(bidWithTargeting)).to.eql(30); }) }) From 3169267013dfcd9e302955873905d78ae9384fc3 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 2 Apr 2026 14:57:36 -0400 Subject: [PATCH 10/10] Update auctionmanager_spec.js --- test/spec/auctionmanager_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 94d762cc239..935486d52c0 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -873,7 +873,7 @@ describe('auctionmanager.js', function () { describe('setConfig(minTargetedBidCacheTTL)', () => { it('should update getMinTargetedBidCacheTTL', () => { expect(getMinTargetedBidCacheTTL()).to.eql(null); - config.setConfig({minTargetedBidCacheTTL: 3600}); + config.setConfig({ minTargetedBidCacheTTL: 3600 }); expect(getMinTargetedBidCacheTTL()).to.eql(3600); });