{"record":{"id":"d9b0796714fdfa11","repo":"vitessio/vitess","slug":"listener-must-be-a-function","errorCode":null,"errorMessage":"listener must be a function","messagePattern":"listener must be a function","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/event/event.go","lineNumber":111,"sourceCode":"\nfunc (why BadListenerError) Error() string {\n\treturn \"bad listener func: \" + string(why)\n}\n\n// AddListener registers a listener function that will be called when a matching\n// event is dispatched. The type of the function's first (and only) argument\n// declares the event type (or interface) to listen for.\nfunc AddListener(fn any) {\n\tlistenersMutex.Lock()\n\tdefer listenersMutex.Unlock()\n\n\tfnType := reflect.TypeOf(fn)\n\n\t// check that the function type is what we think: # of inputs/outputs, etc.\n\t// panic if conditions not met (because it's a programming error to have that happen)\n\tswitch {\n\tcase fnType.Kind() != reflect.Func:\n\t\tpanic(BadListenerError(\"listener must be a function\"))\n\tcase fnType.NumIn() != 1:\n\t\tpanic(BadListenerError(\"listener must take exactly one input argument\"))\n\t}\n\n\t// the first input parameter is the event\n\tevType := fnType.In(0)\n\n\t// keep a list of listeners for each event type\n\tlisteners[evType] = append(listeners[evType], fn)\n\n\t// if eventType is an interface, store it in a separate list\n\t// so we can check non-interface objects against all interfaces\n\tif evType.Kind() == reflect.Interface {\n\t\tinterfaces = append(interfaces, evType)\n\t}\n}\n\n// Dispatch sends an event to all registered listeners that were declared","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/event/event.go#L93-L129","documentation":"event.AddListener reflects on the listener function to validate it takes exactly one input argument matching the dispatched event type; a listener that is not a func at all is rejected immediately with BadListenerError panic. Since this is a programming error, the library panics rather than returning an error.","triggerScenarios":"Calling event.AddListener(listeners, x) where x is not a function — e.g. a struct, nil, or a value of non-func type passed by mistake.","commonSituations":"Passing a method value bound incorrectly; passing the event type instead of a handler; mixing up AddListener with a generic subscribe API that accepts callbacks of any shape.","solutions":["Pass a function taking exactly one parameter of the event type, e.g. func(ev *MyEvent)","Check the value being registered is a func (see type guard below)","Fix the call site so the handler signature matches the event's documented contract"],"exampleFix":"// before\nevent.AddListener(listeners, myStruct)\n// after\nevent.AddListener(listeners, func(ev *myEvent) { /* handle */ })","handlingStrategy":"type-guard","validationCode":"func safeAddListener(ls event.Listeners, fn any) {\n    if reflect.TypeOf(fn) != nil && reflect.TypeOf(fn).Kind() == reflect.Func {\n        event.AddListener(ls, fn)\n    }\n}","typeGuard":"func isFuncListener(fn any) bool {\n    t := reflect.TypeOf(fn)\n    return t != nil && t.Kind() == reflect.Func\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        if _, ok := r.(event.BadListenerError); ok {\n            log.Errorf(\"invalid listener: %v\", r)\n        } else {\n            panic(r)\n        }\n    }\n}()\nevent.AddListener(listeners, candidate)","preventionTips":["Always register handlers with signature func(*EventType)","Type-check listener candidates in wrappers before AddListener","Recover BadListenerError panics in plugin/dynamic registration paths"],"tags":["event","panic","reflection","listener"],"backgroundTag":"invalid-listener-signature","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}