hyperledger/fabric · warning

block deliverer for channel `%s` is already stopped

Error message

block deliverer for channel `%s` is already stopped

What it means

StopDeliverForChannel returns this error when the channel's block deliverer has already entered the stopping state (d.stopping is true under the service lock). It is a guard against double-stop of the delivery service for a channel, not a malfunction.

Source

Thrown at core/deliverservice/deliveryclient.go:304

			return nil, fmt.Errorf("failed to access client TLS configuration: %w", err)
		}
		dcBFT.TLSCertHash = util.ComputeSHA256(cert.Certificate[0])
	}

	dcBFT.Initialize(d.conf.ChannelConfig, "")

	return dcBFT, nil
}

// StopDeliverForChannel stops blocks delivery for channel by stopping channel block provider
func (d *deliverServiceImpl) StopDeliverForChannel() error {
	d.lock.Lock()
	defer d.lock.Unlock()

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

	if d.blockDeliverer == nil {
		errMsg := fmt.Sprintf("block deliverer for channel `%s` is <nil>, can't stop delivery", d.channelID)
		logger.Errorf("Delivery service: %s", errMsg)
		return errors.New(errMsg)
	}
	d.blockDeliverer.Stop()
	d.blockDeliverer = nil

	logger.Debugf("This peer will stop passing blocks from orderer service to other peers on channel: %s", d.channelID)
	return nil
}

// Stop all service and release resources
func (d *deliverServiceImpl) Stop() {
	d.lock.Lock()
	defer d.lock.Unlock()

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Treat this error as benign and idempotent — check for the 'already stopped' message and ignore it
  2. Track stop state on the caller side so StopDeliverForChannel is invoked once per channel
  3. Synchronize concurrent stop calls so only one goroutine stops a given channel
  4. If the channel should be usable again, restart delivery via StartDeliverForChannel instead of re-stopping

Example fix

// before
err := deliverService.StopDeliverForChannel(ch)
if err != nil { return err }
// after
err := deliverService.StopDeliverForChannel(ch)
if err != nil && strings.Contains(err.Error(), "already stopped") {
    return nil // idempotent stop
}
if err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

// check state before calling stop, if exposed by your wrapper
if tracker.IsStopping(channelID) {
    return nil
}

Type guard

func isAlreadyStopped(err error) bool {
    return err != nil && strings.Contains(err.Error(), "is already stopped")
}

Try / catch

if err := svc.StopDeliverForChannel(ch); err != nil {
    if isAlreadyStopped(err) {
        return nil // idempotent
    }
    return err
}

Prevention

When it happens

Trigger: Calling StopDeliverForChannel(channelID) twice for the same channel, or calling it after Stop()/stopping was already initiated (e.g. during peer shutdown or previous stop attempt).

Common situations: Application or lifecycle code invoking stop-delivery in both a shutdown hook and an error-handling path; reconnection logic stopping a channel that the service is concurrently stopping; duplicate channel close events.

Related errors


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