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
- Stop calling KeyMaterial after Close — audit ownership so exactly one owner closes the provider and no consumer outlives it.
- If a provider is shared, use reference counting or clone per consumer instead of closing the shared instance.
- Order shutdown so all in-flight handshakes complete (or the context is cancelled) before Close().
- 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
- Establish single ownership for Close(): exactly one closer per provider.
- For shared providers, reference-count or clone per consumer.
- Order shutdown so all consumers drain before Close() is called.
- Audit every .Close() call on certprovider.Provider values.
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
- pemfile: certificate and key file must be in the same direct
- xds: CertificateProvider to fetch trusted roots is missing,
- xds: CertificateProvider to fetch identity certificate is mi
- no DN found in certificate issuer
- trailing data after AKID extension
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/ef77f3306c83fbd8.
Report an issue: GitHub.