hyperledger/fabric · warning

block deliverer for channel `%s` is stopping

Error message

block deliverer for channel `%s` is stopping

What it means

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.

Source

Thrown at core/deliverservice/deliveryclient.go:109

func NewDeliverService(conf *Config) DeliverService {
	ds := &deliverServiceImpl{
		conf: conf,
	}
	return ds
}

// StartDeliverForChannel starts blocks delivery for channel
// initializes the grpc stream for given chainID, creates blocks provider instance
// that spawns in go routine to read new blocks starting from the position provided by ledger
// info instance.
func (d *deliverServiceImpl) StartDeliverForChannel(chainID string, ledgerInfo blocksprovider.LedgerInfo, finalizer func()) error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.stopping {
		errMsg := fmt.Sprintf("block deliverer for channel `%s` is stopping", chainID)
		logger.Errorf("Delivery service: %s", errMsg)
		return errors.New(errMsg)
	}

	if d.blockDeliverer != nil {
		errMsg := fmt.Sprintf("block deliverer for channel `%s` already exists", chainID)
		logger.Errorf("Delivery service: %s", errMsg)
		return errors.New(errMsg)
	}

	// TODO save the initial bundle in the block deliverer in order to maintain a stand alone BlockVerifier that gets updated
	// immediately after a config block is pulled and verified.
	bundle, err := channelconfig.NewBundle(chainID, d.conf.ChannelConfig, d.conf.CryptoProvider)
	if err != nil {
		return errors.WithMessagef(err, "failed to create block deliverer for channel `%s`", chainID)
	}
	oc, ok := bundle.OrdererConfig()
	if !ok {
		// This should never happen because it is checked in peer.createChannel()
		return errors.Errorf("failed to create block deliverer for channel `%s`, missing OrdererConfig", chainID)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Serialize lifecycle: do not call StartDeliverForChannel after Stop(); check the return error and treat it as expected during shutdown.
  2. Add synchronization in caller code so start attempts complete before Stop is invoked.
  3. If seen persistently while the peer runs (not during shutdown), check for a spurious Stop() call in your integration code.
  4. Log-and-ignore this error in shutdown paths; it is a benign teardown race.

Example fix

// before
client.StartDeliverForChannel(chainID, finalizeFn, stopChan)
client.Stop()
// after
client.Stop()
// do not start afterwards; or guard:
if err := client.StartDeliverForChannel(chainID, finalizeFn, stopChan); err != nil {
    logger.Warningf("delivery not started (shutting down?): %s", err)
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := client.StartDeliverForChannel(chainID, finalize, stopCh); err != nil {
    if strings.Contains(err.Error(), "is stopping") {
        logger.Infof("client shutting down; delivery not started for %s", chainID)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling StartDeliverForChannel after (or concurrently with) DeliveryClient.Stop(); a channel leader election or service creation racing with peer shutdown.

Common situations: 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.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/7601390a38398258. Report an issue: GitHub.