nanomsg.org/go/mangos/v2@v2.0.9-0.20200203084354-8a092611e461/internal/core/socket.go (about)

     1  // Copyright 2019 The Mangos Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package core
    16  
    17  import (
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"nanomsg.org/go/mangos/v2"
    23  	"nanomsg.org/go/mangos/v2/transport"
    24  )
    25  
    26  // Message is just a local alias for mangos.Message
    27  type Message = mangos.Message
    28  
    29  // defaultMaxRxSize is the default maximum Rx size
    30  const defaultMaxRxSize = 1024 * 1024
    31  
    32  const defaultReconnMinTime = time.Millisecond * 100
    33  
    34  const defaultReconnMaxTime = time.Duration(0)
    35  
    36  // socket is the meaty part of the core information.
    37  type socket struct {
    38  	proto mangos.ProtocolBase
    39  
    40  	sync.Mutex
    41  
    42  	closed        bool          // true if Socket was closed at API level
    43  	reconnMinTime time.Duration // reconnect time after error or disconnect
    44  	reconnMaxTime time.Duration // max reconnect interval
    45  	maxRxSize     int           // max recv size
    46  	dialAsynch    bool          // asynchronous dialing?
    47  
    48  	listeners []*listener
    49  	dialers   []*dialer
    50  	pipes     pipeList
    51  	pipehook  mangos.PipeEventHook
    52  }
    53  
    54  type context struct {
    55  	mangos.ProtocolContext
    56  }
    57  
    58  func (s *socket) addPipe(tp transport.Pipe, d *dialer, l *listener) {
    59  	p := newPipe(tp, s, d, l)
    60  
    61  	s.Lock()
    62  	ph := s.pipehook
    63  	s.Unlock()
    64  
    65  	// Add to the list of pipes for the socket; this also reserves an ID
    66  	// for it.
    67  	s.pipes.Add(p)
    68  
    69  	if ph != nil {
    70  		ph(mangos.PipeEventAttaching, p)
    71  	}
    72  
    73  	p.lock.Lock()
    74  	if p.closing {
    75  		p.lock.Lock()
    76  		return
    77  	}
    78  	if s.proto.AddPipe(p) != nil {
    79  		p.lock.Unlock()
    80  		s.pipes.Remove(p)
    81  		go p.close()
    82  		return
    83  	}
    84  	p.added = true
    85  	p.lock.Unlock()
    86  
    87  	if p.d != nil {
    88  		// This call resets the redial time in the dialer.  Its
    89  		// kind of ugly that we have the socket doing this, but
    90  		// the scope is narrow, and it works.
    91  		go p.d.pipeConnected()
    92  	}
    93  	if ph != nil {
    94  		ph(mangos.PipeEventAttached, p)
    95  	}
    96  }
    97  
    98  func (s *socket) remPipe(p *pipe) {
    99  
   100  	s.proto.RemovePipe(p)
   101  
   102  	s.Lock()
   103  	ph := s.pipehook
   104  	s.Unlock()
   105  	s.pipes.Remove(p)
   106  	go func() {
   107  		if ph != nil {
   108  			ph(mangos.PipeEventDetached, p)
   109  		}
   110  		// Don't free the pipe ID until the callback is run, to
   111  		// ensure no use-after-free of the ID itself.
   112  		pipeIDs.Free(p.id)
   113  	}()
   114  }
   115  
   116  func newSocket(proto mangos.ProtocolBase) *socket {
   117  	s := &socket{
   118  		proto:         proto,
   119  		reconnMinTime: defaultReconnMinTime,
   120  		reconnMaxTime: defaultReconnMaxTime,
   121  		maxRxSize:     defaultMaxRxSize,
   122  	}
   123  	return s
   124  }
   125  
   126  // MakeSocket is intended for use by Protocol implementations.  The intention
   127  // is that they can wrap this to provide a "proto.NewSocket()" implementation.
   128  func MakeSocket(proto mangos.ProtocolBase) mangos.Socket {
   129  	return newSocket(proto)
   130  }
   131  
   132  func (s *socket) Close() error {
   133  
   134  	s.Lock()
   135  	listeners := s.listeners
   136  	dialers := s.dialers
   137  
   138  	s.listeners = nil
   139  	s.dialers = nil
   140  	s.closed = true // ensure we don't add new listeners or dialers
   141  	s.Unlock()
   142  
   143  	for _, l := range listeners {
   144  		_ = l.Close()
   145  	}
   146  	for _, d := range dialers {
   147  		_ = d.Close()
   148  	}
   149  
   150  	err := s.proto.Close()
   151  	s.pipes.CloseAll()
   152  	return err
   153  }
   154  
   155  func (ctx context) Send(b []byte) error {
   156  	msg := mangos.NewMessage(len(b))
   157  	msg.Body = append(msg.Body, b...)
   158  	return ctx.SendMsg(msg)
   159  }
   160  func (ctx context) Recv() ([]byte, error) {
   161  	msg, err := ctx.RecvMsg()
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	b := make([]byte, 0, len(msg.Body))
   166  	b = append(b, msg.Body...)
   167  	msg.Free()
   168  	return b, nil
   169  }
   170  
   171  func (s *socket) OpenContext() (mangos.Context, error) {
   172  	c, err := s.proto.OpenContext()
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return &context{c}, nil
   177  }
   178  
   179  func (s *socket) SendMsg(msg *Message) error {
   180  	return s.proto.SendMsg(msg)
   181  }
   182  
   183  func (s *socket) Send(b []byte) error {
   184  	msg := mangos.NewMessage(len(b))
   185  	msg.Body = append(msg.Body, b...)
   186  	return s.SendMsg(msg)
   187  }
   188  
   189  func (s *socket) RecvMsg() (*Message, error) {
   190  	return s.proto.RecvMsg()
   191  }
   192  
   193  func (s *socket) Recv() ([]byte, error) {
   194  	msg, err := s.RecvMsg()
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	b := make([]byte, 0, len(msg.Body))
   199  	b = append(b, msg.Body...)
   200  	msg.Free()
   201  	return b, nil
   202  }
   203  
   204  func (s *socket) getTransport(addr string) transport.Transport {
   205  	var i int
   206  
   207  	if i = strings.Index(addr, "://"); i < 0 {
   208  		return nil
   209  	}
   210  	scheme := addr[:i]
   211  
   212  	return transport.GetTransport(scheme)
   213  }
   214  
   215  func (s *socket) DialOptions(addr string, opts map[string]interface{}) error {
   216  
   217  	d, err := s.NewDialer(addr, opts)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	return d.Dial()
   222  }
   223  
   224  func (s *socket) Dial(addr string) error {
   225  	return s.DialOptions(addr, nil)
   226  }
   227  
   228  func (s *socket) NewDialer(addr string, options map[string]interface{}) (mangos.Dialer, error) {
   229  	t := s.getTransport(addr)
   230  	if t == nil {
   231  		return nil, mangos.ErrBadTran
   232  	}
   233  	td, err := t.NewDialer(addr, s)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	d := &dialer{
   238  		d:             td,
   239  		s:             s,
   240  		reconnMinTime: s.reconnMinTime,
   241  		reconnMaxTime: s.reconnMaxTime,
   242  		asynch:        s.dialAsynch,
   243  		addr:          addr,
   244  	}
   245  	for n, v := range options {
   246  		switch n {
   247  		case mangos.OptionReconnectTime:
   248  			fallthrough
   249  		case mangos.OptionMaxReconnectTime:
   250  			fallthrough
   251  		case mangos.OptionDialAsynch:
   252  			if err := d.SetOption(n, v); err != nil {
   253  				return nil, err
   254  			}
   255  		default:
   256  			if err = td.SetOption(n, v); err != nil {
   257  				return nil, err
   258  			}
   259  		}
   260  	}
   261  	if _, ok := options[mangos.OptionMaxRecvSize]; !ok {
   262  		err = td.SetOption(mangos.OptionMaxRecvSize, s.maxRxSize)
   263  		if err != nil && err != mangos.ErrBadOption {
   264  			return nil, err
   265  		}
   266  	}
   267  
   268  	s.Lock()
   269  	if s.closed {
   270  		s.Unlock()
   271  		_ = d.Close()
   272  		return nil, mangos.ErrClosed
   273  	}
   274  	s.dialers = append(s.dialers, d)
   275  	s.Unlock()
   276  	return d, nil
   277  }
   278  
   279  func (s *socket) ListenOptions(addr string, options map[string]interface{}) error {
   280  	l, err := s.NewListener(addr, options)
   281  	if err != nil {
   282  		return err
   283  	}
   284  	return l.Listen()
   285  }
   286  
   287  func (s *socket) Listen(addr string) error {
   288  	return s.ListenOptions(addr, nil)
   289  }
   290  
   291  func (s *socket) NewListener(addr string, options map[string]interface{}) (mangos.Listener, error) {
   292  	// This function sets up a goroutine to accept inbound connections.
   293  	// The accepted connection will be added to a list of accepted
   294  	// connections.  The Listener just needs to listen continuously,
   295  	// as we assume that we want to continue to receive inbound
   296  	// connections without limit.
   297  	t := s.getTransport(addr)
   298  	if t == nil {
   299  		return nil, mangos.ErrBadTran
   300  	}
   301  	tl, err := t.NewListener(addr, s)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	for n, v := range options {
   306  		if err = tl.SetOption(n, v); err != nil {
   307  			_ = tl.Close()
   308  			return nil, err
   309  		}
   310  	}
   311  	if _, ok := options[mangos.OptionMaxRecvSize]; !ok {
   312  		err = tl.SetOption(mangos.OptionMaxRecvSize, s.maxRxSize)
   313  		if err != nil && err != mangos.ErrBadOption {
   314  			return nil, err
   315  		}
   316  	}
   317  	l := &listener{
   318  		l:    tl,
   319  		s:    s,
   320  		addr: addr,
   321  	}
   322  	s.Lock()
   323  	if s.closed {
   324  		s.Unlock()
   325  		_ = l.Close()
   326  		return nil, mangos.ErrClosed
   327  	}
   328  	s.listeners = append(s.listeners, l)
   329  	s.Unlock()
   330  
   331  	return l, nil
   332  }
   333  
   334  func (s *socket) SetOption(name string, value interface{}) error {
   335  	if err := s.proto.SetOption(name, value); err != mangos.ErrBadOption {
   336  		return err
   337  	}
   338  
   339  	s.Lock()
   340  	defer s.Unlock()
   341  
   342  	switch name {
   343  	case mangos.OptionMaxRecvSize:
   344  		if v, ok := value.(int); ok && v >= 0 {
   345  			s.maxRxSize = v
   346  		} else {
   347  			return mangos.ErrBadValue
   348  		}
   349  	case mangos.OptionReconnectTime:
   350  		if v, ok := value.(time.Duration); ok {
   351  			s.reconnMinTime = v
   352  		} else {
   353  			return mangos.ErrBadValue
   354  		}
   355  	case mangos.OptionMaxReconnectTime:
   356  		if v, ok := value.(time.Duration); ok {
   357  			s.reconnMaxTime = v
   358  		} else {
   359  			return mangos.ErrBadValue
   360  		}
   361  	case mangos.OptionDialAsynch:
   362  		if v, ok := value.(bool); ok {
   363  			s.dialAsynch = v
   364  		} else {
   365  			return mangos.ErrBadValue
   366  		}
   367  	default:
   368  		return mangos.ErrBadOption
   369  	}
   370  	for _, d := range s.dialers {
   371  		_ = d.SetOption(name, value)
   372  	}
   373  	for _, l := range s.listeners {
   374  		_ = l.SetOption(name, value)
   375  	}
   376  	return nil
   377  }
   378  
   379  func (s *socket) GetOption(name string) (interface{}, error) {
   380  	if val, err := s.proto.GetOption(name); err != mangos.ErrBadOption {
   381  		return val, err
   382  	}
   383  
   384  	s.Lock()
   385  	defer s.Unlock()
   386  
   387  	switch name {
   388  	case mangos.OptionMaxRecvSize:
   389  		return s.maxRxSize, nil
   390  	case mangos.OptionReconnectTime:
   391  		return s.reconnMinTime, nil
   392  	case mangos.OptionMaxReconnectTime:
   393  		return s.reconnMaxTime, nil
   394  	case mangos.OptionDialAsynch:
   395  		return s.dialAsynch, nil
   396  	}
   397  	return nil, mangos.ErrBadOption
   398  }
   399  
   400  func (s *socket) Info() mangos.ProtocolInfo {
   401  	return s.proto.Info()
   402  }
   403  
   404  func (s *socket) SetPipeEventHook(newhook mangos.PipeEventHook) mangos.PipeEventHook {
   405  	s.Lock()
   406  	oldhook := s.pipehook
   407  	s.pipehook = newhook
   408  	s.Unlock()
   409  	return oldhook
   410  }