github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/amqp/channelprovider.go (about) 1 package amqp 2 3 import ( 4 "fmt" 5 6 "github.com/pkg/errors" 7 "go.uber.org/atomic" 8 9 "github.com/wfusion/gofusion/common/infra/watermill" 10 11 amqp "github.com/rabbitmq/amqp091-go" 12 ) 13 14 type channel interface { 15 // AMQPChannel returns the underlying AMQP channel. 16 AMQPChannel() *amqp.Channel 17 // DeliveryConfirmationEnabled returns true if delivery confirmation of published messages is enabled. 18 DeliveryConfirmationEnabled() bool 19 // Delivered waits until confirmation of delivery has been received from the AMQP server and returns true if delivery 20 // was successful, otherwise false is returned. If delivery confirmation is not enabled then true is immediately returned. 21 Delivered() bool 22 // Close closes the channel. 23 Close() error 24 } 25 26 type channelProvider interface { 27 Channel() (channel, error) 28 CloseChannel(c channel) error 29 Close() 30 } 31 32 func newChannelProvider(conn *ConnectionWrapper, poolSize int, confirmDelivery bool, 33 logger watermill.LoggerAdapter) (channelProvider, error) { 34 if poolSize == 0 { 35 return newDefaultChannelProvider(conn, confirmDelivery), nil 36 } 37 38 return newPooledChannelProvider(conn, poolSize, confirmDelivery, logger) 39 } 40 41 type pooledChannel struct { 42 logger watermill.LoggerAdapter 43 conn *ConnectionWrapper 44 amqpChan *amqp.Channel 45 closedChan chan *amqp.Error 46 confirmDelivery bool 47 confirmChan chan amqp.Confirmation 48 } 49 50 func newPooledChannel(conn *ConnectionWrapper, logger watermill.LoggerAdapter, confirmDelivery bool) (*pooledChannel, error) { 51 c := &pooledChannel{ 52 logger, 53 conn, 54 nil, 55 nil, 56 confirmDelivery, 57 nil, 58 } 59 60 if err := c.openAMQPChannel(); err != nil { 61 return nil, fmt.Errorf("open AMQP channel: %w", err) 62 } 63 64 return c, nil 65 } 66 67 func (c *pooledChannel) AMQPChannel() *amqp.Channel { 68 return c.amqpChan 69 } 70 71 func (c *pooledChannel) Delivered() bool { 72 if c.confirmChan == nil { 73 // Delivery confirmation is not enabled. Simply return true. 74 return true 75 } 76 77 confirmed := <-c.confirmChan 78 79 return confirmed.Ack 80 } 81 82 // DeliveryConfirmationEnabled returns true if delivery confirmation of published messages is enabled. 83 func (c *pooledChannel) DeliveryConfirmationEnabled() bool { 84 return c.confirmChan != nil 85 } 86 87 func (c *pooledChannel) openAMQPChannel() error { 88 var err error 89 90 c.amqpChan, err = c.conn.amqpConnection.Channel() 91 if err != nil { 92 return fmt.Errorf("create AMQP channel: %w", err) 93 } 94 95 c.closedChan = make(chan *amqp.Error, 1) 96 97 c.amqpChan.NotifyClose(c.closedChan) 98 99 if c.confirmDelivery { 100 err = c.amqpChan.Confirm(false) 101 if err != nil { 102 return fmt.Errorf("confirm AMQP channel: %w", err) 103 } 104 105 c.confirmChan = c.amqpChan.NotifyPublish(make(chan amqp.Confirmation, 1)) 106 } 107 108 return nil 109 } 110 111 func (c *pooledChannel) validate() error { 112 select { 113 case e := <-c.closedChan: 114 c.logger.Info("AMQP channel was closed. Opening new channel.", watermill.LogFields{"close-error": e.Error()}) 115 116 return c.openAMQPChannel() 117 default: 118 return nil 119 } 120 } 121 122 func (c *pooledChannel) Close() error { 123 return c.amqpChan.Close() 124 } 125 126 type channelWrapper struct { 127 *amqp.Channel 128 confirmChan chan amqp.Confirmation 129 } 130 131 func (c *channelWrapper) AMQPChannel() *amqp.Channel { 132 return c.Channel 133 } 134 135 func (c *channelWrapper) DeliveryConfirmationEnabled() bool { 136 return c.confirmChan != nil 137 } 138 139 func (c *channelWrapper) Delivered() bool { 140 if c.confirmChan == nil { 141 // Delivery confirmation is not enabled. Simply return true. 142 return true 143 } 144 145 confirmed := <-c.confirmChan 146 147 return confirmed.Ack 148 } 149 150 // defaultChannelProvider simply opens a new channel when Channel() is called and closes the channel 151 // when CloseChannel is called. 152 type defaultChannelProvider struct { 153 conn *ConnectionWrapper 154 confirmDelivery bool 155 } 156 157 func newDefaultChannelProvider(conn *ConnectionWrapper, confirmDelivery bool) *defaultChannelProvider { 158 return &defaultChannelProvider{conn, confirmDelivery} 159 } 160 161 func (p *defaultChannelProvider) Channel() (channel, error) { 162 amqpChan, err := p.conn.amqpConnection.Channel() 163 if err != nil { 164 return nil, fmt.Errorf("create AMQP channel: %w", err) 165 } 166 167 var confirmChan chan amqp.Confirmation 168 169 if p.confirmDelivery { 170 err = amqpChan.Confirm(false) 171 if err != nil { 172 return nil, fmt.Errorf("confirm AMQP channel: %w", err) 173 } 174 175 confirmChan = amqpChan.NotifyPublish(make(chan amqp.Confirmation, 1)) 176 } 177 178 return &channelWrapper{amqpChan, confirmChan}, nil 179 } 180 181 func (p *defaultChannelProvider) CloseChannel(c channel) error { 182 return c.Close() 183 } 184 185 func (p *defaultChannelProvider) Close() { 186 // Nothing to do. 187 } 188 189 // pooledChannelProvider maintains a pool of channels which are opened immediately upon creation of the provider. 190 // The Channel() function returns an existing channel from the pool. If no channel is available then the caller must 191 // wait until a channel is returned to the pool (with the CloseChannel function). Channels in the pool are closed when 192 // this provider's Close() function is called. 193 // This provider improves performance in high volume systems and also acts as a throttle to prevent the AMQP server from 194 // overloading. 195 type pooledChannelProvider struct { 196 logger watermill.LoggerAdapter 197 conn *ConnectionWrapper 198 channels []*pooledChannel 199 closed atomic.Uint32 200 chanPool chan *pooledChannel 201 closedChan chan struct{} 202 } 203 204 func newPooledChannelProvider(conn *ConnectionWrapper, poolSize int, confirmDelivery bool, 205 logger watermill.LoggerAdapter) (channelProvider, error) { 206 logger.Info("Creating pooled channel provider", watermill.LogFields{"pool-size": poolSize}) 207 208 channels := make([]*pooledChannel, poolSize) 209 210 chanPool := make(chan *pooledChannel, poolSize) 211 212 // Create the channels and add them to the pool. 213 214 for i := 0; i < poolSize; i++ { 215 c, err := newPooledChannel(conn, logger, confirmDelivery) 216 if err != nil { 217 return nil, err 218 } 219 220 channels[i] = c 221 222 chanPool <- c 223 } 224 225 return &pooledChannelProvider{ 226 logger: logger, 227 conn: conn, 228 channels: channels, 229 chanPool: chanPool, 230 closedChan: make(chan struct{}), 231 }, nil 232 } 233 234 func (p *pooledChannelProvider) Channel() (channel, error) { 235 if p.isClosed() { 236 return nil, errors.New("channel pool is closed") 237 } 238 239 select { 240 case c := <-p.chanPool: 241 // Ensure that the existing AMQP channel is still open. 242 if err := c.validate(); err != nil { 243 return nil, err 244 } 245 246 return c, nil 247 248 case <-p.closedChan: 249 return nil, errors.New("provider is closed") 250 } 251 } 252 253 func (p *pooledChannelProvider) CloseChannel(c channel) error { 254 if p.isClosed() { 255 return nil 256 } 257 258 pc, ok := c.(*pooledChannel) 259 if !ok { 260 return errors.New("channel must be of type pooledChannel") 261 } 262 263 p.chanPool <- pc 264 265 return nil 266 } 267 268 func (p *pooledChannelProvider) Close() { 269 if !p.closed.CompareAndSwap(0, 1) { 270 // Already closed. 271 return 272 } 273 274 close(p.closedChan) 275 276 p.logger.Info("Closing all channels in the pool", watermill.LogFields{"pool-size": len(p.channels)}) 277 278 for _, c := range p.channels { 279 if err := c.Close(); err != nil { 280 p.logger.Error("Error closing channel: %s", err, watermill.LogFields{}) 281 } 282 } 283 } 284 285 func (p *pooledChannelProvider) isClosed() bool { 286 return p.closed.Load() != 0 287 }