{"record":{"id":"00bd85a4d306a1cf","repo":"hashicorp/nomad","slug":"subscription-closed","errorCode":null,"errorMessage":"subscription closed","messagePattern":"subscription closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/stream/event_buffer.go","lineNumber":264,"sourceCode":"\t\tlink: &bufferLink{\n\t\t\tnextCh:    make(chan struct{}),\n\t\t\tdroppedCh: make(chan struct{}),\n\t\t},\n\t\tEvents:    events,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\n// Next return the next buffer item in the buffer. It may block until ctx is\n// cancelled or until the next item is published.\nfunc (i *bufferItem) Next(ctx context.Context, forceClose <-chan struct{}) (*bufferItem, error) {\n\t// See if there is already a next value, block if so. Note we don't rely on\n\t// state change (chan nil) as that's not threadsafe but detecting close is.\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase <-forceClose:\n\t\treturn nil, fmt.Errorf(\"subscription closed\")\n\tcase <-i.link.nextCh:\n\t}\n\n\t// Check if the reader is too slow and the event buffer as discarded the event\n\t// This must happen after the above select to prevent a random selection\n\t// between linkCh and droppedCh\n\tselect {\n\tcase <-i.link.droppedCh:\n\t\treturn nil, fmt.Errorf(\"event dropped from buffer\")\n\tdefault:\n\t}\n\n\t// If channel closed, there must be a next item to read\n\tnextRaw := i.link.next.Load()\n\tif nextRaw == nil {\n\t\t// shouldn't be possible\n\t\treturn nil, errors.New(\"invalid next item\")\n\t}","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/stream/event_buffer.go#L246-L282","documentation":"eventBufferSubscription.Next blocks on a select over the caller's context, the link's force-close channel, and nextCh. When the buffer is torn down (shutdown or a competing close path) forceClose fires and Next returns the literal error \"subscription closed\" to signal the iterator is permanently finished. Callers must stop consuming; retrying will keep failing.","triggerScenarios":"Calling Next() on a subscription after the event buffer was closed — e.g. server shutdown, the broker stopping, or EndSubscription/close path triggered — while the consumer was blocked waiting for the next event.","commonSituations":"Nomad server restart or leader change while an HTTP event stream (job events, exec output) is open; client-side code looping over Next() without checking for terminal errors.","solutions":["Stop the consumption loop and unwind the stream — the subscription cannot be resumed.","On the client, reconnect with a fresh Subscribe and resume from the last observed index (falling back to head if 'requested index not in buffer' occurs).","Ensure your loop exits on this error instead of busy-retrying Next().","Handle ctx cancellation alongside it so shutdowns propagate cleanly."],"exampleFix":"// before\nfor { ev, err := sub.Next(ctx); if err != nil { continue } }\n// after\nfor {\n  ev, err := sub.Next(ctx)\n  if err != nil {\n    if err.Error() == \"subscription closed\" { return err } // terminal\n    if ctx.Err() != nil { return ctx.Err() }\n    return err\n  }\n  handle(ev)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isSubscriptionClosed(err error) bool { return err != nil && err.Error() == \"subscription closed\" }","tryCatchPattern":"for {\n  ev, err := sub.Next(ctx)\n  if isSubscriptionClosed(err) || ctx.Err() != nil {\n    return reconnectWithFreshSubscribe(ctx) // terminal: never call Next again\n  }\n  handle(ev)\n}","preventionTips":["Never busy-loop on Next() errors; treat 'subscription closed' as terminal.","Handle server restarts/leader elections as expected stream terminations.","Always pair consumption with context cancellation for clean shutdown."],"tags":["nomad","event-stream","streaming","lifecycle"],"backgroundTag":"stream-closed","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"}