From 9dee0c8437d5714e54c90cc53079b67429112ae2 Mon Sep 17 00:00:00 2001 From: zwl-williamzha Date: Wed, 23 Sep 2026 14:24:06 -0700 Subject: [PATCH] Keep module context data when a module's first hook returns none v4's moduleContexts.put stores whatever the first hook returns, so a nil context gets stored for the module. Every later SetAll on that nil pointer is a no-op, and all data from later stages is silently lost. v3 replaced a nil entry instead (ok && existingCtx != nil), and the move to *ModuleContext in #4603 dropped that. In MSP, ad_cache's entrypoint hook returns no context, so on stage its placement/user/imp ids never reached the bidder request or auction response stages: ad-cache ranking, dynamic floors and FB demote were skipped, and msp_app_level_fill_counter stopped. Co-Authored-By: Claude Opus 5.5 (1M context) --- hooks/hookexecution/context.go | 2 +- hooks/hookexecution/context_test.go | 39 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 hooks/hookexecution/context_test.go diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index d0d8073dda..b2fbda3832 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -54,7 +54,7 @@ func (mc *moduleContexts) put(moduleName string, mCtx *hookstage.ModuleContext) defer mc.Unlock() existingCtx, ok := mc.ctxs[moduleName] - if !ok { + if !ok || existingCtx == nil { mc.ctxs[moduleName] = mCtx return } diff --git a/hooks/hookexecution/context_test.go b/hooks/hookexecution/context_test.go new file mode 100644 index 0000000000..aaab2cebb4 --- /dev/null +++ b/hooks/hookexecution/context_test.go @@ -0,0 +1,39 @@ +package hookexecution + +import ( + "testing" + + "github.com/prebid/prebid-server/v4/hooks/hookstage" + "github.com/stretchr/testify/assert" +) + +func TestModuleContextsPutAfterNil(t *testing.T) { + mcs := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}} + mcs.put("module", nil) + + later := hookstage.NewModuleContext() + later.Set("key", "value") + mcs.put("module", later) + + got, ok := mcs.get("module") + assert.True(t, ok) + v, found := got.Get("key") + assert.True(t, found) + assert.Equal(t, "value", v) +} + +func TestModuleContextsPutMergesAndKeepsNilHarmless(t *testing.T) { + mcs := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}} + + first := hookstage.NewModuleContext() + first.Set("a", 1) + mcs.put("module", first) + mcs.put("module", nil) + + second := hookstage.NewModuleContext() + second.Set("b", 2) + mcs.put("module", second) + + got, _ := mcs.get("module") + assert.Equal(t, map[string]any{"a": 1, "b": 2}, got.GetAll()) +}