{"record":{"id":"57aebef9d3e69919","repo":"hashicorp/nomad","slug":"errallocbroadcasterclosed","errorCode":"ErrAllocBroadcasterClosed","errorMessage":"alloc broadcaster closed","messagePattern":"alloc broadcaster closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/structs/broadcaster.go","lineNumber":21,"sourceCode":"\npackage structs\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/hashicorp/nomad/nomad/structs\"\n)\n\nconst (\n\t// listenerCap is the capacity of the listener chans. Must be exactly 1\n\t// to prevent Sends from blocking and allows them to pop old pending\n\t// updates from the chan before enqueueing the latest update.\n\tlistenerCap = 1\n)\n\nvar ErrAllocBroadcasterClosed = errors.New(\"alloc broadcaster closed\")\n\n// AllocBroadcaster implements an allocation broadcast channel where each\n// listener receives allocation updates. Pending updates are dropped and\n// replaced by newer allocation updates, so listeners may not receive every\n// allocation update. However this ensures Sends never block and listeners only\n// receive the latest allocation update -- never a stale version.\ntype AllocBroadcaster struct {\n\tmu sync.Mutex\n\n\t// listeners is a map of unique ids to listener chans. lazily\n\t// initialized on first listen\n\tlisteners map[int]chan *structs.Allocation\n\n\t// nextId is the next id to assign in listener map\n\tnextId int\n\n\t// closed is true if broadcaster is closed.\n\tclosed bool","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/structs/broadcaster.go#L3-L39","documentation":"ErrAllocBroadcasterClosed is returned by AllocBroadcaster.Send when the broadcaster has already been closed. The broadcaster distributes the latest alloc update to listeners (capacity-1 channels) without blocking; sending after Close is a programming/lifecycle error. Errors.Is/errors.As matching works via the sentinel value.","triggerScenarios":"Calling b.Send(alloc) after b.Close() has been called — e.g. a late update arriving after the client shut down the allocation's broadcaster, as tested in broadcaster_test.go.","commonSituations":"Shutdown races where a hook or watcher still holds a reference and pushes an alloc update after Close; duplicate shutdown paths calling Close then Send.","solutions":["Guard Send with the broadcaster's closed state or check ErrAllocBroadcasterClosed and treat it as benign shutdown noise","Ensure no goroutines retain the broadcaster past its Close","Use errors.Is(err, ErrAllocBroadcasterClosed) to filter it from real failures","Restructure lifecycle so Senders are stopped before Close"],"exampleFix":"// before\nb.Send(alloc) // panics-free but errors after Close\n// after\nif err := b.Send(alloc); err != nil && !errors.Is(err, structs.ErrAllocBroadcasterClosed) {\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := broadcaster.Send(alloc); err != nil {\n    if errors.Is(err, structs.ErrAllocBroadcasterClosed) {\n        return nil // benign: broadcaster shut down, drop the update\n    }\n    return err\n}","preventionTips":["Stop all senders before calling Close on the broadcaster","Never retain the broadcaster beyond its owning allocation's lifecycle","Use errors.Is against ErrAllocBroadcasterClosed to filter shutdown noise","Centralize broadcaster lifecycle management in one owner"],"tags":["go","concurrency","client-structs"],"backgroundTag":"use-after-close","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}