nanomsg.org/go/mangos/v2@v2.0.9-0.20200203084354-8a092611e461/internal/core/dialer.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  	"math/rand"
    19  	"sync"
    20  	"time"
    21  
    22  	"nanomsg.org/go/mangos/v2/errors"
    23  
    24  	"nanomsg.org/go/mangos/v2"
    25  	"nanomsg.org/go/mangos/v2/transport"
    26  )
    27  
    28  type dialer struct {
    29  	sync.Mutex
    30  	d             transport.Dialer
    31  	s             *socket
    32  	addr          string
    33  	closed        bool
    34  	active        bool
    35  	dialing       bool
    36  	asynch        bool
    37  	redialer      *time.Timer
    38  	reconnTime    time.Duration
    39  	reconnMinTime time.Duration
    40  	reconnMaxTime time.Duration
    41  	closeq        chan struct{}
    42  }
    43  
    44  func (d *dialer) Dial() error {
    45  	d.Lock()
    46  	if d.active {
    47  		d.Unlock()
    48  		return mangos.ErrAddrInUse
    49  	}
    50  	if d.closed {
    51  		d.Unlock()
    52  		return mangos.ErrClosed
    53  	}
    54  	d.closeq = make(chan struct{})
    55  	d.active = true
    56  	d.reconnTime = d.reconnMinTime
    57  	if d.asynch {
    58  		go d.redial()
    59  		d.Unlock()
    60  		return nil
    61  	}
    62  	d.Unlock()
    63  	return d.dial(false)
    64  }
    65  
    66  func (d *dialer) Close() error {
    67  	d.Lock()
    68  	defer d.Unlock()
    69  	if d.closed {
    70  		return mangos.ErrClosed
    71  	}
    72  	if d.redialer != nil {
    73  		d.redialer.Stop()
    74  	}
    75  	d.closed = true
    76  	return nil
    77  }
    78  
    79  func (d *dialer) GetOption(n string) (interface{}, error) {
    80  	switch n {
    81  	case mangos.OptionReconnectTime:
    82  		d.Lock()
    83  		v := d.reconnMinTime
    84  		d.Unlock()
    85  		return v, nil
    86  	case mangos.OptionMaxReconnectTime:
    87  		d.Lock()
    88  		v := d.reconnMaxTime
    89  		d.Unlock()
    90  		return v, nil
    91  	case mangos.OptionDialAsynch:
    92  		d.Lock()
    93  		v := d.asynch
    94  		d.Unlock()
    95  		return v, nil
    96  	}
    97  	if val, err := d.d.GetOption(n); err != mangos.ErrBadOption {
    98  		return val, err
    99  	}
   100  	// Pass it up to the socket
   101  	return d.s.GetOption(n)
   102  }
   103  
   104  func (d *dialer) SetOption(n string, v interface{}) error {
   105  	switch n {
   106  	case mangos.OptionReconnectTime:
   107  		if v, ok := v.(time.Duration); ok && v >= 0 {
   108  			d.Lock()
   109  			d.reconnMinTime = v
   110  			d.Unlock()
   111  			return nil
   112  		}
   113  		return mangos.ErrBadValue
   114  	case mangos.OptionMaxReconnectTime:
   115  		if v, ok := v.(time.Duration); ok && v >= 0 {
   116  			d.Lock()
   117  			d.reconnMaxTime = v
   118  			d.Unlock()
   119  			return nil
   120  		}
   121  		return mangos.ErrBadValue
   122  	case mangos.OptionDialAsynch:
   123  		if v, ok := v.(bool); ok {
   124  			d.Lock()
   125  			d.asynch = v
   126  			d.Unlock()
   127  			return nil
   128  		}
   129  		return mangos.ErrBadValue
   130  	}
   131  	// Transport specific options passed down.
   132  	return d.d.SetOption(n, v)
   133  }
   134  
   135  func (d *dialer) Address() string {
   136  	return d.addr
   137  }
   138  
   139  // Socket calls this after the pipe is fully accepted (we got a good
   140  // SP layer connection) -- this way we still get the full backoff if
   141  // we achieve a TCP connect, but the upper layer protocols are mismatched,
   142  // or the remote peer just rejects us (such as if an already connected
   143  // pair pipe.)
   144  func (d *dialer) pipeConnected() {
   145  	d.Lock()
   146  	d.reconnTime = d.reconnMinTime
   147  	d.Unlock()
   148  }
   149  
   150  func (d *dialer) pipeClosed() {
   151  	// We always want to sleep a little bit after the pipe closed down,
   152  	// to avoid spinning hard.  This can happen if we connect, but the
   153  	// peer refuses to accept our protocol.  Injecting at least a little
   154  	// delay should help.
   155  	d.Lock()
   156  	time.AfterFunc(d.reconnTime, d.redial)
   157  	d.Unlock()
   158  }
   159  
   160  func (d *dialer) dial(redial bool) error {
   161  	d.Lock()
   162  	if d.closed {
   163  		d.Unlock()
   164  		return errors.ErrClosed
   165  	}
   166  	if d.asynch {
   167  		redial = true
   168  	}
   169  	d.Unlock()
   170  
   171  	p, err := d.d.Dial()
   172  	if err == nil {
   173  		d.s.addPipe(p, d, nil)
   174  		return nil
   175  	}
   176  
   177  	d.Lock()
   178  	defer d.Unlock()
   179  
   180  	// We're no longer dialing, so let another reschedule happen, if
   181  	// appropriate.   This is quite possibly paranoia.  We should only
   182  	// be in this routine in the following circumstances:
   183  	//
   184  	// 1. Initial dialing (via Dial())
   185  	// 2. After a previously created pipe fails and is closed due to error.
   186  	// 3. After timing out from a failed connection attempt.
   187  
   188  	if !redial {
   189  		return err
   190  	}
   191  	switch err {
   192  	case mangos.ErrClosed:
   193  		// Stop redialing, no further action.
   194  
   195  	default:
   196  		// Exponential backoff, and jitter.  Our backoff grows at
   197  		// about 1.3x on average, so we don't penalize a failed
   198  		// connection too badly.
   199  		minfact := float64(1.1)
   200  		maxfact := float64(1.5)
   201  		actfact := rand.Float64()*(maxfact-minfact) + minfact
   202  		rtime := d.reconnTime
   203  		if d.reconnMaxTime != 0 {
   204  			d.reconnTime = time.Duration(actfact * float64(d.reconnTime))
   205  			if d.reconnTime > d.reconnMaxTime {
   206  				d.reconnTime = d.reconnMaxTime
   207  			}
   208  		}
   209  		d.redialer = time.AfterFunc(rtime, d.redial)
   210  	}
   211  	return err
   212  }
   213  
   214  func (d *dialer) redial() {
   215  	_ = d.dial(true)
   216  }