@@ -27,6 +27,7 @@ import (
2727
2828 "github.com/hashicorp/go-hclog"
2929 "github.com/hashicorp/go-plugin/internal/cmdrunner"
30+ "github.com/hashicorp/go-plugin/internal/grpcmux"
3031 "github.com/hashicorp/go-plugin/runner"
3132 "google.golang.org/grpc"
3233)
6364 // ErrSecureConfigAndReattach is returned when both Reattach and
6465 // SecureConfig are set.
6566 ErrSecureConfigAndReattach = errors .New ("only one of Reattach or SecureConfig can be set" )
67+
68+ // ErrGRPCBrokerMuxNotSupported is returned when the client requests
69+ // multiplexing over the gRPC broker, but the plugin does not support the
70+ // feature. In most cases, this should be resolvable by updating and
71+ // rebuilding the plugin, or restarting the plugin with
72+ // ClientConfig.GRPCBrokerMultiplex set to false.
73+ ErrGRPCBrokerMuxNotSupported = errors .New ("client requested gRPC broker multiplexing but plugin does not support the feature" )
6674)
6775
6876// Client handles the lifecycle of a plugin application. It launches
@@ -102,6 +110,9 @@ type Client struct {
102110 processKilled bool
103111
104112 unixSocketCfg UnixSocketConfig
113+
114+ grpcMuxerOnce sync.Once
115+ grpcMuxer * grpcmux.GRPCClientMuxer
105116}
106117
107118// NegotiatedVersion returns the protocol version negotiated with the server.
@@ -237,6 +248,19 @@ type ClientConfig struct {
237248 // protocol.
238249 GRPCDialOptions []grpc.DialOption
239250
251+ // GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC
252+ // broker will multiplex all brokered gRPC servers over the plugin's original
253+ // listener socket instead of making a new listener for each server. The
254+ // go-plugin library currently only includes a Go implementation for the
255+ // server (i.e. plugin) side of gRPC broker multiplexing.
256+ //
257+ // Does not support reattaching.
258+ //
259+ // Multiplexed gRPC streams MUST be established sequentially, i.e. after
260+ // calling AcceptAndServe from one side, wait for the other side to Dial
261+ // before calling AcceptAndServe again.
262+ GRPCBrokerMultiplex bool
263+
240264 // SkipHostEnv allows plugins to run without inheriting the parent process'
241265 // environment variables.
242266 SkipHostEnv bool
@@ -352,7 +376,7 @@ func CleanupClients() {
352376 wg .Wait ()
353377}
354378
355- // Creates a new plugin client which manages the lifecycle of an external
379+ // NewClient creates a new plugin client which manages the lifecycle of an external
356380// plugin and gets the address for the RPC connection.
357381//
358382// The client must be cleaned up at some point by calling Kill(). If
@@ -374,10 +398,10 @@ func NewClient(config *ClientConfig) (c *Client) {
374398 }
375399
376400 if config .SyncStdout == nil {
377- config .SyncStdout = ioutil .Discard
401+ config .SyncStdout = io .Discard
378402 }
379403 if config .SyncStderr == nil {
380- config .SyncStderr = ioutil .Discard
404+ config .SyncStderr = io .Discard
381405 }
382406
383407 if config .AllowedProtocols == nil {
@@ -572,6 +596,10 @@ func (c *Client) Start() (addr net.Addr, err error) {
572596 if c .config .SecureConfig != nil && c .config .Reattach != nil {
573597 return nil , ErrSecureConfigAndReattach
574598 }
599+
600+ if c .config .GRPCBrokerMultiplex && c .config .Reattach != nil {
601+ return nil , fmt .Errorf ("gRPC broker multiplexing is not supported with Reattach config" )
602+ }
575603 }
576604
577605 if c .config .Reattach != nil {
@@ -603,6 +631,9 @@ func (c *Client) Start() (addr net.Addr, err error) {
603631 fmt .Sprintf ("PLUGIN_MAX_PORT=%d" , c .config .MaxPort ),
604632 fmt .Sprintf ("PLUGIN_PROTOCOL_VERSIONS=%s" , strings .Join (versionStrings , "," )),
605633 }
634+ if c .config .GRPCBrokerMultiplex {
635+ env = append (env , fmt .Sprintf ("%s=true" , envMultiplexGRPC ))
636+ }
606637
607638 cmd := c .config .Cmd
608639 if cmd == nil {
@@ -790,7 +821,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
790821 // Trim the line and split by "|" in order to get the parts of
791822 // the output.
792823 line = strings .TrimSpace (line )
793- parts := strings .SplitN (line , "|" , 6 )
824+ parts := strings .Split (line , "|" )
794825 if len (parts ) < 4 {
795826 errText := fmt .Sprintf ("Unrecognized remote plugin message: %s" , line )
796827 if ! ok {
@@ -878,6 +909,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
878909 return nil , fmt .Errorf ("error parsing server cert: %s" , err )
879910 }
880911 }
912+
913+ if c .config .GRPCBrokerMultiplex && c .protocol == ProtocolGRPC {
914+ if len (parts ) <= 6 {
915+ return nil , fmt .Errorf ("%w; for Go plugins, you will need to update the " +
916+ "github.com/hashicorp/go-plugin dependency and recompile" , ErrGRPCBrokerMuxNotSupported )
917+ }
918+ if muxSupported , err := strconv .ParseBool (parts [6 ]); err != nil {
919+ return nil , fmt .Errorf ("error parsing %q as a boolean for gRPC broker multiplexing support" , parts [6 ])
920+ } else if ! muxSupported {
921+ return nil , ErrGRPCBrokerMuxNotSupported
922+ }
923+ }
881924 }
882925
883926 c .address = addr
@@ -951,12 +994,11 @@ func (c *Client) reattach() (net.Addr, error) {
951994
952995 if c .config .Reattach .Test {
953996 c .negotiatedVersion = c .config .Reattach .ProtocolVersion
954- }
955-
956- // If we're in test mode, we do NOT set the process. This avoids the
957- // process being killed (the only purpose we have for c.process), since
958- // in test mode the process is responsible for exiting on its own.
959- if ! c .config .Reattach .Test {
997+ } else {
998+ // If we're in test mode, we do NOT set the runner. This avoids the
999+ // runner being killed (the only purpose we have for setting c.runner
1000+ // when reattaching), since in test mode the process is responsible for
1001+ // exiting on its own.
9601002 c .runner = r
9611003 }
9621004
@@ -1061,11 +1103,24 @@ func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error)
10611103// dialer is compatible with grpc.WithDialer and creates the connection
10621104// to the plugin.
10631105func (c * Client ) dialer (_ string , timeout time.Duration ) (net.Conn , error ) {
1064- conn , err := netAddrDialer (c .address )( "" , timeout )
1106+ muxer , err := c . getGRPCMuxer (c .address )
10651107 if err != nil {
10661108 return nil , err
10671109 }
10681110
1111+ var conn net.Conn
1112+ if muxer .Enabled () {
1113+ conn , err = muxer .Dial ()
1114+ if err != nil {
1115+ return nil , err
1116+ }
1117+ } else {
1118+ conn , err = netAddrDialer (c .address )("" , timeout )
1119+ if err != nil {
1120+ return nil , err
1121+ }
1122+ }
1123+
10691124 // If we have a TLS config we wrap our connection. We only do this
10701125 // for net/rpc since gRPC uses its own mechanism for TLS.
10711126 if c .protocol == ProtocolNetRPC && c .config .TLSConfig != nil {
@@ -1075,6 +1130,22 @@ func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
10751130 return conn , nil
10761131}
10771132
1133+ func (c * Client ) getGRPCMuxer (addr net.Addr ) (* grpcmux.GRPCClientMuxer , error ) {
1134+ if c .protocol != ProtocolGRPC || ! c .config .GRPCBrokerMultiplex {
1135+ return nil , nil
1136+ }
1137+
1138+ var err error
1139+ c .grpcMuxerOnce .Do (func () {
1140+ c .grpcMuxer , err = grpcmux .NewGRPCClientMuxer (c .logger , addr )
1141+ })
1142+ if err != nil {
1143+ return nil , err
1144+ }
1145+
1146+ return c .grpcMuxer , nil
1147+ }
1148+
10781149var stdErrBufferSize = 64 * 1024
10791150
10801151func (c * Client ) logStderr (name string , r io.Reader ) {
0 commit comments