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 }