func (c *Client) Kill() {
	c.l.Lock()
	process := c.process
	addr := c.address
	c.l.Unlock()
	if process == nil {
		return
	}
	defer func() {
		c.clientWaitGroup.Wait()
		c.l.Lock()
		c.process = nil
		c.l.Unlock()
	}()
	graceful := false
	if addr != nil {
		client, err := c.Client()
		if err == nil {
			err = client.Close()
			graceful = err == nil
			if err != nil {
				c.logger.Warn("error closing client during Kill", "err", err)
			}
		} else {
			c.logger.Error("client", "error", err)
		}
	}
	if graceful {
		select {
		case <-c.doneCtx.Done():
			c.logger.Debug("plugin exited")
			return
		case <-time.After(2 * time.Second):
		}
	}
	c.logger.Warn("plugin failed to exit gracefully")
	process.Kill()
	c.l.Lock()
	c.processKilled = true
	c.l.Unlock()
}
End the executing subprocess (if it is running) and perform any cleanup
tasks necessary such as capturing any remaining logs and so on.

This method blocks until the process successfully exits.

This method can safely be called multiple times.
package plugin
import (
	"bufio"
	"context"
	"crypto/subtle"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	hclog "github.com/hashicorp/go-hclog"
)
var Killed uint32 = 0
var managedClients = make([]*Client, 0, 5)
var managedClientsLock sync.Mutex
var (
	ErrProcessNotFound = errors.New("Reattachment process not found")
	ErrChecksumsDoNotMatch = errors.New("checksums did not match")
	ErrSecureConfigNoChecksum = errors.New("no checksum provided")
	ErrSecureConfigNoHash = errors.New("no hash implementation provided")
	ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set")
)
type Client struct {
	config            *ClientConfig
	exited            bool
	l                 sync.Mutex
	address           net.Addr
	process           *os.Process
	client            ClientProtocol
	protocol          Protocol
	logger            hclog.Logger
	doneCtx           context.Context
	ctxCancel         context.CancelFunc
	negotiatedVersion int
	clientWaitGroup sync.WaitGroup
	stderrWaitGroup sync.WaitGroup
	processKilled bool
}
func (c *Client) NegotiatedVersion() int {
	return c.negotiatedVersion
}
type ClientConfig struct {
	HandshakeConfig
	Plugins PluginSet
	VersionedPlugins map[int]PluginSet
	Cmd      *exec.Cmd
	Reattach *ReattachConfig
	SecureConfig *SecureConfig
	TLSConfig *tls.Config
	Managed bool
	MinPort, MaxPort uint
	StartTimeout time.Duration
	Stderr io.Writer
	SyncStdout io.Writer
	SyncStderr io.Writer
	AllowedProtocols []Protocol
	Logger hclog.Logger
	AutoMTLS bool
}
type ReattachConfig struct {
	Protocol Protocol
	Addr     net.Addr
	Pid      int
}
type SecureConfig struct {
	Checksum []byte
	Hash     hash.Hash
}
func (s *SecureConfig) Check(filePath string) (bool, error) {
	if len(s.Checksum) == 0 {
		return false, ErrSecureConfigNoChecksum
	}
	if s.Hash == nil {
		return false, ErrSecureConfigNoHash
	}
	file, err := os.Open(filePath)
	if err != nil {
		return false, err
	}
	defer file.Close()
	_, err = io.Copy(s.Hash, file)
	if err != nil {
		return false, err
	}
	sum := s.Hash.Sum(nil)
	return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil
}
func CleanupClients() {
	atomic.StoreUint32(&Killed, 1)
	var wg sync.WaitGroup
	managedClientsLock.Lock()
	for _, client := range managedClients {
		wg.Add(1)
		go func(client *Client) {
			client.Kill()
			wg.Done()
		}(client)
	}
	managedClientsLock.Unlock()
	wg.Wait()
}
func NewClient(config *ClientConfig) (c *Client) {
	if config.MinPort == 0 && config.MaxPort == 0 {
		config.MinPort = 10000
		config.MaxPort = 25000
	}
	if config.StartTimeout == 0 {
		config.StartTimeout = 1 * time.Minute
	}
	if config.Stderr == nil {
		config.Stderr = ioutil.Discard
	}
	if config.SyncStdout == nil {
		config.SyncStdout = ioutil.Discard
	}
	if config.SyncStderr == nil {
		config.SyncStderr = ioutil.Discard
	}
	if config.AllowedProtocols == nil {
		config.AllowedProtocols = []Protocol{ProtocolNetRPC}
	}
	if config.Logger == nil {
		config.Logger = hclog.New(&hclog.LoggerOptions{
			Output: hclog.DefaultOutput,
			Level:  hclog.Trace,
			Name:   "plugin",
		})
	}
	c = &Client{
		config: config,
		logger: config.Logger,
	}
	if config.Managed {
		managedClientsLock.Lock()
		managedClients = append(managedClients, c)
		managedClientsLock.Unlock()
	}
	return
}
func (c *Client) Client() (ClientProtocol, error) {
	_, err := c.Start()
	if err != nil {
		return nil, err
	}
	c.l.Lock()
	defer c.l.Unlock()
	if c.client != nil {
		return c.client, nil
	}
	switch c.protocol {
	case ProtocolNetRPC:
		c.client, err = newRPCClient(c)
	case ProtocolGRPC:
		c.client, err = newGRPCClient(c.doneCtx, c)
	default:
		return nil, fmt.Errorf("unknown server protocol: %s", c.protocol)
	}
	if err != nil {
		c.client = nil
		return nil, err
	}
	return c.client, nil
}
func (c *Client) Exited() bool {
	c.l.Lock()
	defer c.l.Unlock()
	return c.exited
}
func (c *Client) killed() bool {
	c.l.Lock()
	defer c.l.Unlock()
	return c.processKilled
}