code wiki / _hdl_build / nx_adnet_reconcile.nx

nx_adnet_reconcile.nx source

↩ module page · 93 lines · 4323 B

1// nx_adnet_reconcile.nx -- LIB: the BEAT that owns campaign field 7 (spent_milli). 2// WHY IT EXISTS: camp_may_serve enforces a budget ceiling by reading spend from the campaign ROW, so the 3// serve path stays O(1) and never scans a journal. Something has to WRITE that field, and it must not be 4// the serve path -- ainv_spent_milli walks three journals, which with 158k served rows is a per-request 5// table scan on the hot path. Putting that behind an ad impression is how an ad network takes its own 6// site down. So: a beat reconciles, serving reads. 7// 8// ★ PURE TRANSFORM, no IO. Takes the confs as buffers and emits the NEW conf into out. The caller owns 9// reading and the atomic write, which keeps this gateable with fixtures and keeps the dangerous part 10// (overwriting a live conf) in one reviewable place. 11// ★ PRESERVES EVERYTHING IT DOES NOT UNDERSTAND. Comment rows, blank lines and rows whose spend cannot be 12// computed pass through BYTE-IDENTICAL. A reconciler that drops what it cannot parse is a data-loss bug 13// wearing a maintenance-task costume. 14// ★ AN UNMEASURABLE SPEND IS LEFT ALONE, NEVER ZEROED. ainv_spent_milli returns -1 when a campaign is 15// unpriced, its id is malformed, or viewable > served (a forged beacon). Writing 0 there would silently 16// REVIVE an exhausted campaign -- the exact failure the ceiling exists to prevent. The old value stands 17// and camp_may_serve keeps refusing. 18// license_tier: ORIGINAL 19import "nx_syscalls.nx" 20import "_hdl_build/nx_adnet_campaign.nx" 21import "_hdl_build/nx_adnet_invoice.nx" 22 23func arec_catd(dst: *u8, off: i64, v: i64) -> i64 { 24 var o: i64 = off 25 var m: i64 = v 26 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m } 27 let t: *u8 = sys_mmap(24) 28 var k: i64 = 0 29 if m == 0 { t[0] = 48 as u8; k = 1 } 30 while m > 0 { t[k] = (48 + (m - (m / 10) * 10)) as u8; m = m / 10; k = k + 1 } 31 var i: i64 = k - 1 32 while i >= 0 { dst[o] = t[i]; o = o + 1; i = i - 1 } 33 return o 34} 35 36// copy fields 0..6 of a campaign row verbatim, then append the freshly computed spend as field 7. 37func arec_row_with_spend(row: *u8, rlen: i64, spent: i64, out: *u8, cap: i64) -> i64 { 38 if cap < rlen + 32 { return 0 } 39 var o: i64 = 0 40 var fs: i64 = 0 41 var f: i64 = 0 42 while f < 7 { 43 let fe: i64 = ad_tab(row, fs, rlen) 44 var k: i64 = fs 45 while k < fe { out[o] = row[k]; o = o + 1; k = k + 1 } 46 out[o] = 9 as u8; o = o + 1 47 fs = fe + 1 48 f = f + 1 49 } 50 o = arec_catd(out, o, spent) 51 out[o] = 0 as u8 52 return o 53} 54 55// Emit a refreshed campaign conf. Returns bytes written. 56// Rows are rewritten ONLY when a spend can actually be computed; everything else survives untouched. 57func arec_refresh(camps: *u8, clen: i64, rates: *u8, rlen: i64, slog: *u8, sl: i64, vlog: *u8, vl: i64, clog: *u8, cl: i64, out: *u8, cap: i64) -> i64 { 58 var o: i64 = 0 59 let id: *u8 = sys_mmap(128) 60 var ls: i64 = 0 61 while ls < clen { 62 let le: i64 = ad_eol(camps, ls, clen) 63 let rowlen: i64 = le - ls 64 let row: *u8 = ((camps as i64) + ls) as *u8 65 var wrote: i64 = 0 66 if rowlen > 0 { 67 if row[0] != (35 as u8) { 68 aslot_field_b(row, rowlen, 0, id, 128) 69 if aslot_id_ok(id) == 1 { 70 let sp: i64 = ainv_spent_milli(rates, rlen, slog, sl, vlog, vl, clog, cl, id) 71 // -1 = UNMEASURABLE. Leave the row exactly as it was: writing 0 would revive an 72 // exhausted campaign, which is the one thing a spend reconciler must never do. 73 if sp >= 0 { 74 if o + rowlen + 64 < cap { 75 let w: i64 = arec_row_with_spend(row, rowlen, sp, ((out as i64) + o) as *u8, cap - o) 76 if w > 0 { o = o + w; wrote = 1 } 77 } 78 } 79 } 80 } 81 } 82 if wrote == 0 { 83 // byte-identical passthrough for comments, blanks, and anything unmeasurable 84 if o + rowlen + 2 < cap { 85 var k: i64 = 0 86 while k < rowlen { out[o] = row[k]; o = o + 1; k = k + 1 } 87 } 88 } 89 out[o] = 10 as u8; o = o + 1 90 ls = le + 1 91 } 92 out[o] = 0 as u8 93 return o 94}