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 }