Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for early auction close with video + done cb cleanup #3024

Merged
merged 5 commits into from
Sep 11, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 1 addition & 7 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -630,13 +630,7 @@ export function PrebidServer() {
utils.logError('error parsing response: ', result.status);
}

const videoBid = bids.some(bidResponse => bidResponse.bid.mediaType === 'video');
const cacheEnabled = config.getConfig('cache.url');

// video bids with cache enabled need to be cached first before they are considered done
if (!(videoBid && cacheEnabled)) {
done();
}
done();
doClientSideSyncs(requestedBidders);
}

Expand Down
6 changes: 2 additions & 4 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,7 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbac
if (s2sBidRequest.ad_units.length) {
let doneCbs = serverBidRequests.map(bidRequest => {
bidRequest.start = timestamp();
bidRequest.doneCbCallCount = 0;
return doneCb(bidRequest.bidderRequestId)
return doneCb;
});

// only log adapters that actually have adUnit bids
Expand Down Expand Up @@ -372,12 +371,11 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbac
utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
bidRequest.doneCbCallCount = 0;
let done = doneCb(bidRequest.bidderRequestId);
let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? {
request: requestCallbacks.request.bind(null, bidRequest.bidderCode),
done: requestCallbacks.done
} : undefined);
adapter.callBids(bidRequest, addBidResponse, done, ajax);
adapter.callBids(bidRequest, addBidResponse, doneCb, ajax);
});
}

Expand Down
18 changes: 1 addition & 17 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { userSync } from 'src/userSync';
import { nativeBidIsValid } from 'src/native';
import { isValidVideoBid } from 'src/video';
import CONSTANTS from 'src/constants.json';
import events from 'src/events';
import includes from 'core-js/library/fn/array/includes';

import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest } from 'src/utils';
Expand Down Expand Up @@ -177,22 +176,7 @@ export function newBidder(spec) {
// register any required usersync pixels.
const responses = [];
function afterAllResponses(bids) {
const bidsArray = bids ? (bids[0] ? bids : [bids]) : [];

const videoBid = bidsArray.some(bid => bid.mediaType === 'video');
const cacheEnabled = config.getConfig('cache.url');

// video bids with cache enabled need to be cached first before they are considered done
if (!(videoBid && cacheEnabled)) {
done();
}

// TODO: the code above needs to be refactored. We should always call done when we're done. if the auction
// needs to do cleanup before _it_ can be done it should handle that itself in the auction. It should _not_
// require us, the bidders, to conditionally call done. That makes the whole done API very flaky.
// As soon as that is refactored, we can move this emit event where it should be, within the done function.
events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest);

done();
registerSyncs(responses, bidderRequest.gdprConsent);
}

Expand Down
110 changes: 67 additions & 43 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
* @property {function(): void} callBids - sends requests to all adapters for bids
*/

import { uniques, flatten, timestamp, adUnitsFilter, delayExecution, getBidderRequest } from './utils';
import { uniques, flatten, timestamp, adUnitsFilter, getBidderRequest } from './utils';
import { getPriceBucketString } from './cpmBucketManager';
import { getNativeTargeting } from './native';
import { getCacheUrl, store } from './videoCache';
Expand Down Expand Up @@ -155,29 +155,21 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
}
}

function done(bidRequestId) {
const innerBidRequestId = bidRequestId;
return delayExecution(function() {
const request = find(_bidderRequests, (bidRequest) => {
return innerBidRequestId === bidRequest.bidderRequestId;
});

// this is done for cache-enabled video bids in tryAddVideoBid, after the cache is stored
request.doneCbCallCount += 1;
bidsBackAll();
}, 1);
function done(bidderCount) {
let doneCalled = 0;
return function() {
doneCalled++;
if (doneCalled === bidderCount) {
Copy link
Member

Choose a reason for hiding this comment

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

are we concerned that some bidders will call multiple done callbacks causing the auction to close early?

closeAuction();
}
}
}

/**
* Execute bidBackHandler if all bidders have called done.
*/
function bidsBackAll() {
if (_bidderRequests.every((bidRequest) => bidRequest.doneCbCallCount >= 1)) {
// when all bidders have called done callback atleast once it means auction is complete
utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived);
_auctionStatus = AUCTION_COMPLETED;
executeCallback(false, true);
}
function closeAuction() {
// when all bidders have called done callback atleast once it means auction is complete
utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived);
_auctionStatus = AUCTION_COMPLETED;
executeCallback(false, true);
}

function callBids() {
Expand Down Expand Up @@ -206,7 +198,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
};
events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit);

adaptermanager.callBids(_adUnits, bidRequests, addBidResponse.bind(this), done.bind(this), {
let callbacks = newCallbacks(done(bidRequests.length), this);
let boundObj = {
localAddBidResponse: callbacks.addBidResponse
}
adaptermanager.callBids(_adUnits, bidRequests, addBidResponse.bind(boundObj), callbacks.adapterDone, {
request(source, origin) {
increment(outstandingRequests, origin);
increment(requests, source);
Expand Down Expand Up @@ -288,7 +284,6 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
addBidReceived,
executeCallback,
callBids,
bidsBackAll,
addWinningBid,
getWinningBids: () => _winningBids,
getTimeout: () => _timeout,
Expand All @@ -301,6 +296,51 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
}
}

export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) {
this.localAddBidResponse(adUnitCode, bid);
}, 'addBidResponse');

function newCallbacks(done, auctionInstance) {
let outstandingBidsAdded = 0;
let doneCalled = false;

function afterBidAdded() {
outstandingBidsAdded--;
if (doneCalled && outstandingBidsAdded === 0) {
done()
}
}

function addBidResponse(adUnitCode, bid) {
outstandingBidsAdded++;
let bidRequests = auctionInstance.getBidRequests();
let auctionId = auctionInstance.getAuctionId();

let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode);
let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId});

if (bidResponse.mediaType === 'video') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should add logic (here?) to clarify only for instream video adUnits, since outstream video adUnits shouldn't need to be cached.

tryAddVideoBid(auctionInstance, bidResponse, bidRequest, afterBidAdded);
} else {
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}

function adapterDone() {
Copy link
Member

Choose a reason for hiding this comment

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

what do we need adapterDone for?

// events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can remove commented code (if we truly do not need to emit this event here).

doneCalled = true;
if ((outstandingBidsAdded === 0)) {
done();
}
}

return {
addBidResponse,
adapterDone
}
}

function doCallbacksIfTimedout(auctionInstance, bidResponse) {
if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) {
auctionInstance.executeCallback(true);
Expand All @@ -316,7 +356,7 @@ function addBidToAuction(auctionInstance, bidResponse) {
}

// Video bids may fail if the cache is down, or there's trouble on the network.
function tryAddVideoBid(auctionInstance, bidResponse, bidRequest) {
function tryAddVideoBid(auctionInstance, bidResponse, bidRequest, afterBidAdded) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could remove bidRequest from arguments here since it doesn't appear to be used now with these new changes.

let addBid = true;
if (config.getConfig('cache.url')) {
if (!bidResponse.videoCacheKey) {
Expand All @@ -331,10 +371,8 @@ function tryAddVideoBid(auctionInstance, bidResponse, bidRequest) {
if (!bidResponse.vastUrl) {
bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey);
}
// only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll
bidRequest.doneCbCallCount += 1;
addBidToAuction(auctionInstance, bidResponse);
auctionInstance.bidsBackAll();
afterBidAdded();
}
});
} else if (!bidResponse.vastUrl) {
Expand All @@ -344,24 +382,10 @@ function tryAddVideoBid(auctionInstance, bidResponse, bidRequest) {
}
if (addBid) {
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}

export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) {
let auctionInstance = this;
let bidRequests = auctionInstance.getBidRequests();
let auctionId = auctionInstance.getAuctionId();

let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode);
let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId});

if (bidResponse.mediaType === 'video') {
tryAddVideoBid(auctionInstance, bidResponse, bidRequest);
} else {
addBidToAuction(auctionInstance, bidResponse);
}
}, 'addBidResponse');

// Postprocess the bids so that all the universal properties exist, no matter which bidder they came from.
// This should be called before addBidToAuction().
function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) {
Expand Down