{"record":{"id":"7601390a38398258","repo":"hyperledger/fabric","slug":"block-deliverer-for-channel-s-is-stopping","errorCode":null,"errorMessage":"block deliverer for channel `%s` is stopping","messagePattern":"block deliverer for channel `(.+?)` is stopping","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/deliverservice/deliveryclient.go","lineNumber":109,"sourceCode":"func NewDeliverService(conf *Config) DeliverService {\n\tds := &deliverServiceImpl{\n\t\tconf: conf,\n\t}\n\treturn ds\n}\n\n// StartDeliverForChannel starts blocks delivery for channel\n// initializes the grpc stream for given chainID, creates blocks provider instance\n// that spawns in go routine to read new blocks starting from the position provided by ledger\n// info instance.\nfunc (d *deliverServiceImpl) StartDeliverForChannel(chainID string, ledgerInfo blocksprovider.LedgerInfo, finalizer func()) error {\n\td.lock.Lock()\n\tdefer d.lock.Unlock()\n\n\tif d.stopping {\n\t\terrMsg := fmt.Sprintf(\"block deliverer for channel `%s` is stopping\", chainID)\n\t\tlogger.Errorf(\"Delivery service: %s\", errMsg)\n\t\treturn errors.New(errMsg)\n\t}\n\n\tif d.blockDeliverer != nil {\n\t\terrMsg := fmt.Sprintf(\"block deliverer for channel `%s` already exists\", chainID)\n\t\tlogger.Errorf(\"Delivery service: %s\", errMsg)\n\t\treturn errors.New(errMsg)\n\t}\n\n\t// TODO save the initial bundle in the block deliverer in order to maintain a stand alone BlockVerifier that gets updated\n\t// immediately after a config block is pulled and verified.\n\tbundle, err := channelconfig.NewBundle(chainID, d.conf.ChannelConfig, d.conf.CryptoProvider)\n\tif err != nil {\n\t\treturn errors.WithMessagef(err, \"failed to create block deliverer for channel `%s`\", chainID)\n\t}\n\toc, ok := bundle.OrdererConfig()\n\tif !ok {\n\t\t// This should never happen because it is checked in peer.createChannel()\n\t\treturn errors.Errorf(\"failed to create block deliverer for channel `%s`, missing OrdererConfig\", chainID)","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/deliverservice/deliveryclient.go#L91-L127","documentation":"DeliveryClient.StartDeliverForChannel returns this when the client is in the stopping state (d.stopping set, e.g. Stop() was called during shutdown). Starting a new block deliverer at that moment is refused because the client is being torn down.","triggerScenarios":"Calling StartDeliverForChannel after (or concurrently with) DeliveryClient.Stop(); a channel leader election or service creation racing with peer shutdown.","commonSituations":"Peer shutting down while gossip/leader code still tries to start block delivery; lifecycle races in tests stopping the client while a goroutine starts delivery; reconfiguration scripts restarting the peer with in-flight client calls.","solutions":["Serialize lifecycle: do not call StartDeliverForChannel after Stop(); check the return error and treat it as expected during shutdown.","Add synchronization in caller code so start attempts complete before Stop is invoked.","If seen persistently while the peer runs (not during shutdown), check for a spurious Stop() call in your integration code.","Log-and-ignore this error in shutdown paths; it is a benign teardown race."],"exampleFix":"// before\nclient.StartDeliverForChannel(chainID, finalizeFn, stopChan)\nclient.Stop()\n// after\nclient.Stop()\n// do not start afterwards; or guard:\nif err := client.StartDeliverForChannel(chainID, finalizeFn, stopChan); err != nil {\n    logger.Warningf(\"delivery not started (shutting down?): %s\", err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := client.StartDeliverForChannel(chainID, finalize, stopCh); err != nil {\n    if strings.Contains(err.Error(), \"is stopping\") {\n        logger.Infof(\"client shutting down; delivery not started for %s\", chainID)\n        return nil\n    }\n    return err\n}","preventionTips":["Order lifecycle strictly: all starts complete before calling Stop().","Use a WaitGroup or context cancellation so goroutines finish starting before teardown.","Treat this error as benign during shutdown; do not retry against a stopped client.","Alert only if it occurs while the peer is nominally running."],"tags":["delivery-service","shutdown","race-condition"],"backgroundTag":"client-already-stopped","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}