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