go-micro.dev/v5@v5.12.0/broker/rabbitmq/connection.go (about) 1 package rabbitmq 2 3 // 4 // All credit to Mondo 5 // 6 7 import ( 8 "crypto/tls" 9 "regexp" 10 "strings" 11 "sync" 12 "time" 13 14 amqp "github.com/rabbitmq/amqp091-go" 15 "go-micro.dev/v5/logger" 16 ) 17 18 type MQExchangeType string 19 20 const ( 21 ExchangeTypeFanout MQExchangeType = "fanout" 22 ExchangeTypeTopic = "topic" 23 ExchangeTypeDirect = "direct" 24 ) 25 26 var ( 27 DefaultExchange = Exchange{ 28 Name: "micro", 29 Type: ExchangeTypeTopic, 30 } 31 DefaultRabbitURL = "amqp://guest:guest@127.0.0.1:5672" 32 DefaultPrefetchCount = 0 33 DefaultPrefetchGlobal = false 34 DefaultRequeueOnError = false 35 DefaultConfirmPublish = false 36 DefaultWithoutExchange = false 37 38 // The amqp library does not seem to set these when using amqp.DialConfig 39 // (even though it says so in the comments) so we set them manually to make 40 // sure to not brake any existing functionality. 41 defaultHeartbeat = 10 * time.Second 42 defaultLocale = "en_US" 43 44 defaultAmqpConfig = amqp.Config{ 45 Heartbeat: defaultHeartbeat, 46 Locale: defaultLocale, 47 } 48 49 dial = amqp.Dial 50 dialTLS = amqp.DialTLS 51 dialConfig = amqp.DialConfig 52 ) 53 54 type rabbitMQConn struct { 55 Connection *amqp.Connection 56 Channel *rabbitMQChannel 57 ExchangeChannel *rabbitMQChannel 58 exchange Exchange 59 withoutExchange bool 60 url string 61 prefetchCount int 62 prefetchGlobal bool 63 confirmPublish bool 64 65 sync.Mutex 66 connected bool 67 close chan bool 68 69 waitConnection chan struct{} 70 71 logger logger.Logger 72 } 73 74 // Exchange is the rabbitmq exchange. 75 type Exchange struct { 76 // Name of the exchange 77 Name string 78 // Type of the exchange 79 Type MQExchangeType 80 // Whether its persistent 81 Durable bool 82 } 83 84 func newRabbitMQConn(ex Exchange, urls []string, prefetchCount int, prefetchGlobal bool, confirmPublish bool, withoutExchange bool, logger logger.Logger) *rabbitMQConn { 85 var url string 86 87 if len(urls) > 0 && regexp.MustCompile("^amqp(s)?://.*").MatchString(urls[0]) { 88 url = urls[0] 89 } else { 90 url = DefaultRabbitURL 91 } 92 93 ret := &rabbitMQConn{ 94 exchange: ex, 95 url: url, 96 withoutExchange: withoutExchange, 97 prefetchCount: prefetchCount, 98 prefetchGlobal: prefetchGlobal, 99 confirmPublish: confirmPublish, 100 close: make(chan bool), 101 waitConnection: make(chan struct{}), 102 logger: logger, 103 } 104 // its bad case of nil == waitConnection, so close it at start 105 close(ret.waitConnection) 106 return ret 107 } 108 109 func (r *rabbitMQConn) connect(secure bool, config *amqp.Config) error { 110 // try connect 111 if err := r.tryConnect(secure, config); err != nil { 112 return err 113 } 114 115 // connected 116 r.Lock() 117 r.connected = true 118 r.Unlock() 119 120 // create reconnect loop 121 go r.reconnect(secure, config) 122 return nil 123 } 124 125 func (r *rabbitMQConn) reconnect(secure bool, config *amqp.Config) { 126 // skip first connect 127 var connect bool 128 129 for { 130 if connect { 131 // try reconnect 132 if err := r.tryConnect(secure, config); err != nil { 133 time.Sleep(1 * time.Second) 134 continue 135 } 136 137 // connected 138 r.Lock() 139 r.connected = true 140 r.Unlock() 141 // unblock resubscribe cycle - close channel 142 //at this point channel is created and unclosed - close it without any additional checks 143 close(r.waitConnection) 144 } 145 146 connect = true 147 notifyClose := make(chan *amqp.Error) 148 r.Connection.NotifyClose(notifyClose) 149 chanNotifyClose := make(chan *amqp.Error) 150 var channel *amqp.Channel 151 if !r.withoutExchange { 152 channel = r.ExchangeChannel.channel 153 } else { 154 channel = r.Channel.channel 155 } 156 channel.NotifyClose(chanNotifyClose) 157 // To avoid deadlocks it is necessary to consume the messages from all channels. 158 for notifyClose != nil || chanNotifyClose != nil { 159 // block until closed 160 select { 161 case err := <-chanNotifyClose: 162 r.logger.Log(logger.ErrorLevel, err) 163 // block all resubscribe attempt - they are useless because there is no connection to rabbitmq 164 // create channel 'waitConnection' (at this point channel is nil or closed, create it without unnecessary checks) 165 r.Lock() 166 r.connected = false 167 r.waitConnection = make(chan struct{}) 168 r.Unlock() 169 chanNotifyClose = nil 170 case err := <-notifyClose: 171 r.logger.Log(logger.ErrorLevel, err) 172 // block all resubscribe attempt - they are useless because there is no connection to rabbitmq 173 // create channel 'waitConnection' (at this point channel is nil or closed, create it without unnecessary checks) 174 r.Lock() 175 r.connected = false 176 r.waitConnection = make(chan struct{}) 177 r.Unlock() 178 notifyClose = nil 179 case <-r.close: 180 return 181 } 182 } 183 } 184 } 185 186 func (r *rabbitMQConn) Connect(secure bool, config *amqp.Config) error { 187 r.Lock() 188 189 // already connected 190 if r.connected { 191 r.Unlock() 192 return nil 193 } 194 195 // check it was closed 196 select { 197 case <-r.close: 198 r.close = make(chan bool) 199 default: 200 // no op 201 // new conn 202 } 203 204 r.Unlock() 205 206 return r.connect(secure, config) 207 } 208 209 func (r *rabbitMQConn) Close() error { 210 r.Lock() 211 defer r.Unlock() 212 213 select { 214 case <-r.close: 215 return nil 216 default: 217 close(r.close) 218 r.connected = false 219 } 220 221 return r.Connection.Close() 222 } 223 224 func (r *rabbitMQConn) tryConnect(secure bool, config *amqp.Config) error { 225 var err error 226 227 if config == nil { 228 config = &defaultAmqpConfig 229 } 230 231 url := r.url 232 233 if secure || config.TLSClientConfig != nil || strings.HasPrefix(r.url, "amqps://") { 234 if config.TLSClientConfig == nil { 235 config.TLSClientConfig = &tls.Config{ 236 InsecureSkipVerify: true, 237 } 238 } 239 240 url = strings.Replace(r.url, "amqp://", "amqps://", 1) 241 } 242 243 r.Connection, err = dialConfig(url, *config) 244 245 if err != nil { 246 return err 247 } 248 249 if r.Channel, err = newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal, r.confirmPublish); err != nil { 250 return err 251 } 252 253 if !r.withoutExchange { 254 if r.exchange.Durable { 255 r.Channel.DeclareDurableExchange(r.exchange) 256 } else { 257 r.Channel.DeclareExchange(r.exchange) 258 } 259 r.ExchangeChannel, err = newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal, r.confirmPublish) 260 } 261 return err 262 } 263 264 func (r *rabbitMQConn) Consume(queue, key string, headers amqp.Table, qArgs amqp.Table, autoAck, durableQueue bool) (*rabbitMQChannel, <-chan amqp.Delivery, error) { 265 consumerChannel, err := newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal, r.confirmPublish) 266 if err != nil { 267 return nil, nil, err 268 } 269 270 if durableQueue { 271 err = consumerChannel.DeclareDurableQueue(queue, qArgs) 272 } else { 273 err = consumerChannel.DeclareQueue(queue, qArgs) 274 } 275 276 if err != nil { 277 return nil, nil, err 278 } 279 280 deliveries, err := consumerChannel.ConsumeQueue(queue, autoAck) 281 if err != nil { 282 return nil, nil, err 283 } 284 285 if !r.withoutExchange { 286 err = consumerChannel.BindQueue(queue, key, r.exchange.Name, headers) 287 if err != nil { 288 return nil, nil, err 289 } 290 } 291 292 return consumerChannel, deliveries, nil 293 } 294 295 func (r *rabbitMQConn) Publish(exchange, key string, msg amqp.Publishing) error { 296 if r.withoutExchange { 297 return r.Channel.Publish("", key, msg) 298 } 299 return r.ExchangeChannel.Publish(exchange, key, msg) 300 }