github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/scale_proto.go (about)

     1  package reader
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/Jeffail/benthos/v3/lib/log"
    12  	"github.com/Jeffail/benthos/v3/lib/message"
    13  	"github.com/Jeffail/benthos/v3/lib/metrics"
    14  	"github.com/Jeffail/benthos/v3/lib/types"
    15  	"go.nanomsg.org/mangos/v3"
    16  	"go.nanomsg.org/mangos/v3/protocol/pull"
    17  	"go.nanomsg.org/mangos/v3/protocol/sub"
    18  
    19  	// Import all transport types
    20  	_ "go.nanomsg.org/mangos/v3/transport/all"
    21  )
    22  
    23  //------------------------------------------------------------------------------
    24  
    25  // ScaleProtoConfig contains configuration fields for the ScaleProto input type.
    26  type ScaleProtoConfig struct {
    27  	URLs        []string `json:"urls" yaml:"urls"`
    28  	Bind        bool     `json:"bind" yaml:"bind"`
    29  	SocketType  string   `json:"socket_type" yaml:"socket_type"`
    30  	SubFilters  []string `json:"sub_filters" yaml:"sub_filters"`
    31  	PollTimeout string   `json:"poll_timeout" yaml:"poll_timeout"`
    32  	RepTimeout  string   `json:"reply_timeout" yaml:"reply_timeout"`
    33  }
    34  
    35  // NewScaleProtoConfig creates a new ScaleProtoConfig with default values.
    36  func NewScaleProtoConfig() ScaleProtoConfig {
    37  	return ScaleProtoConfig{
    38  		URLs:        []string{"tcp://*:5555"},
    39  		Bind:        true,
    40  		SocketType:  "PULL",
    41  		SubFilters:  []string{},
    42  		PollTimeout: "5s",
    43  		RepTimeout:  "5s",
    44  	}
    45  }
    46  
    47  //------------------------------------------------------------------------------
    48  
    49  // ScaleProto is an input type that contains Scalability Protocols messages.
    50  type ScaleProto struct {
    51  	socket mangos.Socket
    52  	cMut   sync.Mutex
    53  
    54  	pollTimeout time.Duration
    55  	repTimeout  time.Duration
    56  
    57  	urls  []string
    58  	conf  ScaleProtoConfig
    59  	stats metrics.Type
    60  	log   log.Modular
    61  }
    62  
    63  // NewScaleProto creates a new ScaleProto input type.
    64  func NewScaleProto(conf ScaleProtoConfig, log log.Modular, stats metrics.Type) (*ScaleProto, error) {
    65  	s := ScaleProto{
    66  		conf:  conf,
    67  		stats: stats,
    68  		log:   log,
    69  	}
    70  
    71  	for _, u := range conf.URLs {
    72  		for _, splitU := range strings.Split(u, ",") {
    73  			if len(splitU) > 0 {
    74  				// TODO: V4 Remove this work around
    75  				s.urls = append(s.urls, strings.Replace(splitU, "//*:", "//0.0.0.0:", 1))
    76  			}
    77  		}
    78  	}
    79  
    80  	if conf.SocketType == "SUB" && len(conf.SubFilters) == 0 {
    81  		return nil, errors.New("must provide at least one sub filter when connecting with a SUB socket, in order to subscribe to all messages add an empty string")
    82  	}
    83  
    84  	if tout := conf.PollTimeout; len(tout) > 0 {
    85  		var err error
    86  		if s.pollTimeout, err = time.ParseDuration(tout); err != nil {
    87  			return nil, fmt.Errorf("failed to parse poll timeout string: %v", err)
    88  		}
    89  	}
    90  	if tout := conf.RepTimeout; len(tout) > 0 {
    91  		var err error
    92  		if s.repTimeout, err = time.ParseDuration(tout); err != nil {
    93  			return nil, fmt.Errorf("failed to parse reply timeout string: %v", err)
    94  		}
    95  	}
    96  
    97  	return &s, nil
    98  }
    99  
   100  //------------------------------------------------------------------------------
   101  
   102  // getSocketFromType returns a socket based on a socket type string.
   103  func getSocketFromType(t string) (mangos.Socket, error) {
   104  	switch t {
   105  	case "PULL":
   106  		return pull.NewSocket()
   107  	case "SUB":
   108  		return sub.NewSocket()
   109  	}
   110  	return nil, types.ErrInvalidScaleProtoType
   111  }
   112  
   113  //------------------------------------------------------------------------------
   114  
   115  // Connect establishes a nanomsg socket.
   116  func (s *ScaleProto) Connect() error {
   117  	return s.ConnectWithContext(context.Background())
   118  }
   119  
   120  // ConnectWithContext establishes a nanomsg socket.
   121  func (s *ScaleProto) ConnectWithContext(ctx context.Context) error {
   122  	s.cMut.Lock()
   123  	defer s.cMut.Unlock()
   124  
   125  	if s.socket != nil {
   126  		return nil
   127  	}
   128  
   129  	var socket mangos.Socket
   130  	var err error
   131  
   132  	defer func() {
   133  		if err != nil && socket != nil {
   134  			socket.Close()
   135  		}
   136  	}()
   137  
   138  	socket, err = getSocketFromType(s.conf.SocketType)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	if s.conf.Bind {
   144  		for _, addr := range s.urls {
   145  			if err = socket.Listen(addr); err != nil {
   146  				break
   147  			}
   148  		}
   149  	} else {
   150  		for _, addr := range s.urls {
   151  			if err = socket.Dial(addr); err != nil {
   152  				break
   153  			}
   154  		}
   155  	}
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	// TODO: This is only used for request/response sockets, and is invalid with
   161  	// other socket types.
   162  	// err = socket.SetOption(mangos.OptionSendDeadline, s.pollTimeout)
   163  	// if err != nil {
   164  	// 	return err
   165  	// }
   166  
   167  	// Set timeout to prevent endless lock.
   168  	err = socket.SetOption(mangos.OptionRecvDeadline, s.repTimeout)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	for _, filter := range s.conf.SubFilters {
   174  		if err := socket.SetOption(mangos.OptionSubscribe, []byte(filter)); err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	if s.conf.Bind {
   180  		s.log.Infof(
   181  			"Receiving Scalability Protocols messages at bound URLs: %s\n",
   182  			s.urls,
   183  		)
   184  	} else {
   185  		s.log.Infof(
   186  			"Receiving Scalability Protocols messages at connected URLs: %s\n",
   187  			s.urls,
   188  		)
   189  	}
   190  
   191  	s.socket = socket
   192  	return nil
   193  }
   194  
   195  // Read attempts to read a new message from the nanomsg socket.
   196  func (s *ScaleProto) Read() (types.Message, error) {
   197  	msg, _, err := s.ReadWithContext(context.Background())
   198  	return msg, err
   199  }
   200  
   201  // ReadWithContext attempts to read a new message from the nanomsg socket.
   202  func (s *ScaleProto) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) {
   203  	s.cMut.Lock()
   204  	socket := s.socket
   205  	s.cMut.Unlock()
   206  
   207  	if socket == nil {
   208  		return nil, nil, types.ErrNotConnected
   209  	}
   210  	data, err := socket.Recv()
   211  	if err != nil {
   212  		if err == mangos.ErrRecvTimeout {
   213  			return nil, nil, types.ErrTimeout
   214  		}
   215  		return nil, nil, err
   216  	}
   217  	return message.New([][]byte{data}), noopAsyncAckFn, nil
   218  }
   219  
   220  // Acknowledge instructs whether the pending messages were propagated
   221  // successfully.
   222  func (s *ScaleProto) Acknowledge(err error) error {
   223  	return nil
   224  }
   225  
   226  // CloseAsync shuts down the ScaleProto input and stops processing requests.
   227  func (s *ScaleProto) CloseAsync() {
   228  	s.cMut.Lock()
   229  	if s.socket != nil {
   230  		s.socket.Close()
   231  		s.socket = nil
   232  	}
   233  	s.cMut.Unlock()
   234  }
   235  
   236  // WaitForClose blocks until the ScaleProto input has closed down.
   237  func (s *ScaleProto) WaitForClose(timeout time.Duration) error {
   238  	return nil
   239  }
   240  
   241  //------------------------------------------------------------------------------