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  }