Repository: pingcap/tidb
File: pkg/meta/autoid/autoid_service.go (Go)

Code before:
		return cli, atomic.LoadUint64(&d.version), nil
	}
	d.mu.RUnlock()

	d.mu.Lock()
	defer d.mu.Unlock()
	if d.mu.AutoIDAllocClient != nil {
		return d.mu.AutoIDAllocClient, atomic.LoadUint64(&d.version), nil
	}
	// write a for loop to retry in case of etcd connection error.
	var resp *clientv3.GetResponse
	var err error
	var bo backoffer
retry:
	resp, err = d.etcdCli.Get(ctx, GetAutoIDServiceLeaderEtcdPath(keyspaceID), clientv3.WithFirstCreate()...)
	if err != nil {
		return nil, 0, errors.Trace(err)
	}
	if len(resp.Kvs) == 0 {
		// If the key is not found, it means the autoid service leader is not elected yet.
		// We can retry to get the leader.
		select {
		case <-ctx.Done():
			return nil, 0, errors.Trace(ctx.Err())
		default:
			bo.Backoff()
		}
		goto retry
	}
	bo.Reset()

	addr := string(resp.Kvs[0].Value)
	opt := grpc.WithTransportCredentials(insecure.NewCredentials())
	security := config.GetGlobalConfig().Security
	if len(security.ClusterSSLCA) != 0 {
		clusterSecurity := security.ClusterSecurity()
		tlsConfig, err := clusterSecurity.ToTLSConfig()
		if err != nil {
			return nil, 0, errors.Trace(err)
		}
		opt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
	}
	logutil.BgLogger().Info("connect to leader", zap.String("category", "autoid client"), zap.String("addr", addr))
	grpcConn, err := grpc.NewClient(addr, opt)
	if err != nil {
		return nil, 0, errors.Trace(err)
	}
	cli = autoid.NewAutoIDAllocClient(grpcConn)
	d.mu.AutoIDAllocClient = cli
	d.mu.ClientConn = grpcConn
	return cli, atomic.LoadUint64(&d.version), nil

Review comment (performance):
There's a `time.Sleep` inside `bo.Backoff()`. If this goroutine sleeps, it will not see `<-ctx.Done()` and latency also increases. So IMO the idea way to handle it is

```
select {
case <-ctx.Done():
  ...
case <-time.After(...): // should not use default branch, the goroutine should be blocked at select statement.
}
```

However because the maximum backoff time has been changed to 100ms it's also acceptable even if code sleeps when context is done.

And for current code, we can refine it to

```
if err := ctx.Err(); err != nil { return ... }
bo.Backoff()
goto retry
```

Because there's only one case in select statement, we can directly check `ctx.Err()`. The function comment of it says not nil error is the same as `<-ctx.Done()`

Code after:
		return cli, atomic.LoadUint64(&d.version), nil
	}
	d.mu.RUnlock()

	d.mu.Lock()
	defer d.mu.Unlock()
	if d.mu.AutoIDAllocClient != nil {
		return d.mu.AutoIDAllocClient, atomic.LoadUint64(&d.version), nil
	}
	// write a for loop to retry in case of etcd connection error.
	var resp *clientv3.GetResponse
	var err error
	var bo backoffer
retry:
	resp, err = d.etcdCli.Get(ctx, GetAutoIDServiceLeaderEtcdPath(keyspaceID), clientv3.WithFirstCreate()...)
	if err != nil {
		return nil, 0, errors.Trace(err)
	}
	if len(resp.Kvs) == 0 {
		// If the key is not found, it means the autoid service leader is not elected yet.
		// We can retry to get the leader.
		if err := ctx.Err(); err != nil {
			return nil, 0, errors.Trace(err)
		}
		bo.Backoff()
		goto retry
	}
	bo.Reset()

	addr := string(resp.Kvs[0].Value)
	opt := grpc.WithTransportCredentials(insecure.NewCredentials())
	security := config.GetGlobalConfig().Security
	if len(security.ClusterSSLCA) != 0 {
		clusterSecurity := security.ClusterSecurity()
		tlsConfig, err := clusterSecurity.ToTLSConfig()
		if err != nil {
			return nil, 0, errors.Trace(err)
		}
		opt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
	}
	logutil.BgLogger().Info("connect to leader", zap.String("category", "autoid client"), zap.String("addr", addr))
	grpcConn, err := grpc.NewClient(addr, opt)
	if err != nil {
		return nil, 0, errors.Trace(err)
	}
	cli = autoid.NewAutoIDAllocClient(grpcConn)
	d.mu.AutoIDAllocClient = cli
	d.mu.ClientConn = grpcConn
	return cli, atomic.LoadUint64(&d.version), nil
}