Repository: etcd-io/etcd
File: server/embed/etcd.go (Go)

Code before:
// Etcd contains a running etcd server and its listeners.
type Etcd struct {
	Peers   []*peerListener
	Clients []net.Listener
	// a map of contexts for the servers that serves client requests.
	sctxs            map[string]*serveCtx
	metricsListeners []net.Listener

	tracingExporterShutdown func()

	Server *etcdserver.EtcdServer

	cfg   Config
	stopc chan struct{}
	errc  chan error

	closeOnce sync.Once
	wg        sync.WaitGroup
}

type peerListener struct {
	net.Listener
	serve func() error
	close func(context.Context) error
}

// StartEtcd launches the etcd server and HTTP handlers for client/server communication.
// The returned Etcd.Server is not guaranteed to have joined the cluster. Wait
// on the Etcd.Server.ReadyNotify() channel to know when it completes and is ready for use.
func StartEtcd(inCfg *Config) (e *Etcd, err error) {
	if err = inCfg.Validate(); err != nil {
		return nil, err
	}
	serving := false
	e = &Etcd{cfg: *inCfg, stopc: make(chan struct{})}
	cfg := &e.cfg
	defer func() {
		if e == nil || err == nil {
			return
		}
		if !serving {
			// errored before starting gRPC server for serveCtx.serversC
			for _, sctx := range e.sctxs {
				sctx.close()
			}
		}
		e.Close()
		e = nil
	}()

	if !cfg.SocketOpts.Empty() {

Review comment (suggestion):
Using wg to drain errc is a new concept introduced in previous PR, looks like previously we dependent on us correctly predicting needed capacity https://github.com/etcd-io/etcd/blob/c9045d650e6c1cb2e5361723adf3bb8c6ea22ee0/server/embed/etcd.go#L257
Possibly issues stem from the fact that this logic got outdated, I think now we can have even 4 writers to errc per sctx.

If the wg setup is correct then we should safe with setting capacity of errc channel to 1.

Code after:
// Etcd contains a running etcd server and its listeners.
type Etcd struct {
	Peers   []*peerListener
	Clients []net.Listener
	// a map of contexts for the servers that serves client requests.
	sctxs            map[string]*serveCtx
	metricsListeners []net.Listener

	tracingExporterShutdown func()

	Server *etcdserver.EtcdServer

	cfg Config

	// closeOnce is to ensure `stopc` is closed only once, no matter
	// how many times the Close() method is called.
	closeOnce sync.Once
	// stopc is used to notify the sub goroutines not to send
	// any errors to `errc`.
	stopc chan struct{}
	// errc is used to receive error from sub goroutines (including
	// client handler, peer handler and metrics handler). It's closed
	// after all these sub goroutines exit (checked via `wg`). Writers
	// should avoid writing after `stopc` is closed by selecting on
	// reading from `stopc`.
	errc chan error

	// wg is used to track the lifecycle of all sub goroutines created by `StartEtcd`.
	wg sync.WaitGroup
}

type peerListener struct {
	net.Listener
	serve func() error
	close func(context.Context) error
}

// StartEtcd launches the etcd server and HTTP handlers for client/server communication.
// The returned Etcd.Server is not guaranteed to have joined the cluster. Wait
// on the Etcd.Server.ReadyNotify() channel to know when it completes and is ready for use.
func StartEtcd(inCfg *Config) (e *Etcd, err error) {
	if err = inCfg.Validate(); err != nil {
		return nil, err
	}
	serving := false
	e = &Etcd{cfg: *inCfg, stopc: make(chan struct{})}
	cfg := &e.cfg
	defer func() {
		if e == nil || err == nil {
			return
		}