grpc/grpc-go · error

provider instance is closed

Error message

provider instance is closed

What it means

errProviderClosed (credentials/tls/certprovider/provider.go:44) is returned by Distributor.KeyMaterial once the provider/distributor has been closed. The Provider interface (lines 83-90) exposes Close() to release resources; after Close is invoked, any subsequent KeyMaterial(ctx) call on the same provider instance returns this sentinel. It indicates a use-after-close lifecycle bug in the caller.

Source

Thrown at credentials/tls/certprovider/provider.go:44

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"

	"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle"
	"google.golang.org/grpc/internal"
)

func init() {
	internal.GetCertificateProviderBuilder = getBuilder
}

var (
	// errProviderClosed is returned by Distributor.KeyMaterial when it is
	// closed.
	errProviderClosed = errors.New("provider instance is closed")

	// m is a map from name to Provider builder.
	m = make(map[string]Builder)
)

// Register registers the Provider builder, whose name as returned by its Name()
// method will be used as the name registered with this builder. Registered
// Builders are used by the Store to create Providers.
func Register(b Builder) {
	m[b.Name()] = b
}

// getBuilder returns the Provider builder registered with the given name.
// If no builder is registered with the provided name, nil will be returned.
func getBuilder(name string) Builder {
	if b, ok := m[name]; ok {
		return b
	}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Stop calling KeyMaterial after Close — audit ownership so exactly one owner closes the provider and no consumer outlives it.
  2. If a provider is shared, use reference counting or clone per consumer instead of closing the shared instance.
  3. Order shutdown so all in-flight handshakes complete (or the context is cancelled) before Close().
  4. Search the codebase for .Close() on provider/certprovider objects and ensure each is the single, final use.

Example fix

// before
p, _ := pemfile.NewProvider(opts)
km1, _ := p.KeyMaterial(ctx)
p.Close()
km2, _ := p.KeyMaterial(ctx) // err: provider instance is closed

// after
p, _ := pemfile.NewProvider(opts)
km, _ := p.KeyMaterial(ctx)
// ... use km ...
p.Close() // close only after the last consumer is done
Defensive patterns

Strategy: validation

Try / catch

km, err := provider.KeyMaterial(ctx)
if err != nil {
    if errors.Is(err, certprovider.ErrProviderClosed) /* or internal sentinel */ {
        // provider already closed; stop using it
        }
}

Prevention

When it happens

Trigger: Calling provider.Close() (directly or indirectly — e.g. HandshakeInfo.close at handshake_info.go:116-122 closes its root/identity providers) and then calling KeyMaterial on that provider again. Common when a credential provider is shared but closed by one consumer while another still reads it.

Common situations: Sharing a certprovider.Provider between channels that are torn down at different times; xDS reconfiguration closing providers that a concurrent handshake still references; manual Close() called too early in a shutdown sequence; double-close of a HandshakeInfo.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/ef77f3306c83fbd8. Report an issue: GitHub.