github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/gcp_pubsub.go (about)

     1  package reader
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"sync"
     7  	"time"
     8  
     9  	"cloud.google.com/go/pubsub"
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/message"
    12  	"github.com/Jeffail/benthos/v3/lib/message/batch"
    13  	"github.com/Jeffail/benthos/v3/lib/message/metadata"
    14  	"github.com/Jeffail/benthos/v3/lib/metrics"
    15  	"github.com/Jeffail/benthos/v3/lib/types"
    16  )
    17  
    18  //------------------------------------------------------------------------------
    19  
    20  // GCPPubSubConfig contains configuration values for the input type.
    21  type GCPPubSubConfig struct {
    22  	ProjectID              string `json:"project" yaml:"project"`
    23  	SubscriptionID         string `json:"subscription" yaml:"subscription"`
    24  	MaxOutstandingMessages int    `json:"max_outstanding_messages" yaml:"max_outstanding_messages"`
    25  	MaxOutstandingBytes    int    `json:"max_outstanding_bytes" yaml:"max_outstanding_bytes"`
    26  	Sync                   bool   `json:"sync" yaml:"sync"`
    27  	// TODO: V4 Remove these.
    28  	MaxBatchCount int                `json:"max_batch_count" yaml:"max_batch_count"`
    29  	Batching      batch.PolicyConfig `json:"batching" yaml:"batching"`
    30  }
    31  
    32  // NewGCPPubSubConfig creates a new Config with default values.
    33  func NewGCPPubSubConfig() GCPPubSubConfig {
    34  	return GCPPubSubConfig{
    35  		ProjectID:              "",
    36  		SubscriptionID:         "",
    37  		MaxOutstandingMessages: pubsub.DefaultReceiveSettings.MaxOutstandingMessages,
    38  		MaxOutstandingBytes:    pubsub.DefaultReceiveSettings.MaxOutstandingBytes,
    39  		Sync:                   false,
    40  		MaxBatchCount:          1,
    41  		Batching:               batch.NewPolicyConfig(),
    42  	}
    43  }
    44  
    45  //------------------------------------------------------------------------------
    46  
    47  // GCPPubSub is a benthos reader.Type implementation that reads messages from
    48  // a GCP Cloud Pub/Sub subscription.
    49  type GCPPubSub struct {
    50  	conf GCPPubSubConfig
    51  
    52  	subscription *pubsub.Subscription
    53  	msgsChan     chan *pubsub.Message
    54  	closeFunc    context.CancelFunc
    55  	subMut       sync.Mutex
    56  
    57  	client      *pubsub.Client
    58  	pendingMsgs []*pubsub.Message
    59  
    60  	log   log.Modular
    61  	stats metrics.Type
    62  }
    63  
    64  // NewGCPPubSub creates a new GCP pubsub reader.Type.
    65  func NewGCPPubSub(
    66  	conf GCPPubSubConfig,
    67  	log log.Modular,
    68  	stats metrics.Type,
    69  ) (*GCPPubSub, error) {
    70  	client, err := pubsub.NewClient(context.Background(), conf.ProjectID)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	return &GCPPubSub{
    75  		conf:   conf,
    76  		log:    log,
    77  		stats:  stats,
    78  		client: client,
    79  	}, nil
    80  }
    81  
    82  // Connect attempts to establish a connection to the target subscription.
    83  func (c *GCPPubSub) Connect() error {
    84  	return c.ConnectWithContext(context.Background())
    85  }
    86  
    87  // ConnectWithContext attempts to establish a connection to the target
    88  // subscription.
    89  func (c *GCPPubSub) ConnectWithContext(ignored context.Context) error {
    90  	c.subMut.Lock()
    91  	defer c.subMut.Unlock()
    92  	if c.subscription != nil {
    93  		return nil
    94  	}
    95  
    96  	sub := c.client.Subscription(c.conf.SubscriptionID)
    97  	sub.ReceiveSettings.MaxOutstandingMessages = c.conf.MaxOutstandingMessages
    98  	sub.ReceiveSettings.MaxOutstandingBytes = c.conf.MaxOutstandingBytes
    99  	sub.ReceiveSettings.Synchronous = c.conf.Sync
   100  
   101  	subCtx, cancel := context.WithCancel(context.Background())
   102  	msgsChan := make(chan *pubsub.Message, c.conf.MaxBatchCount)
   103  
   104  	c.subscription = sub
   105  	c.msgsChan = msgsChan
   106  	c.closeFunc = cancel
   107  
   108  	go func() {
   109  		rerr := sub.Receive(subCtx, func(ctx context.Context, m *pubsub.Message) {
   110  			select {
   111  			case msgsChan <- m:
   112  			case <-ctx.Done():
   113  				if m != nil {
   114  					m.Nack()
   115  				}
   116  			}
   117  		})
   118  		if rerr != nil && rerr != context.Canceled {
   119  			c.log.Errorf("Subscription error: %v\n", rerr)
   120  		}
   121  		c.subMut.Lock()
   122  		c.subscription = nil
   123  		close(c.msgsChan)
   124  		c.msgsChan = nil
   125  		c.closeFunc = nil
   126  		c.subMut.Unlock()
   127  	}()
   128  
   129  	c.log.Infof("Receiving GCP Cloud Pub/Sub messages from project '%v' and subscription '%v'\n", c.conf.ProjectID, c.conf.SubscriptionID)
   130  	return nil
   131  }
   132  
   133  // ReadWithContext attempts to read a new message from the target subscription.
   134  func (c *GCPPubSub) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) {
   135  	c.subMut.Lock()
   136  	msgsChan := c.msgsChan
   137  	c.subMut.Unlock()
   138  	if msgsChan == nil {
   139  		return nil, nil, types.ErrNotConnected
   140  	}
   141  
   142  	msg := message.New(nil)
   143  
   144  	var gmsg *pubsub.Message
   145  	var open bool
   146  	select {
   147  	case gmsg, open = <-msgsChan:
   148  	case <-ctx.Done():
   149  		return nil, nil, types.ErrTimeout
   150  	}
   151  	if !open {
   152  		return nil, nil, types.ErrNotConnected
   153  	}
   154  
   155  	part := message.NewPart(gmsg.Data)
   156  	part.SetMetadata(metadata.New(gmsg.Attributes))
   157  	part.Metadata().Set("gcp_pubsub_publish_time_unix", strconv.FormatInt(gmsg.PublishTime.Unix(), 10))
   158  	msg.Append(part)
   159  
   160  	return msg, func(ctx context.Context, res types.Response) error {
   161  		if res.Error() != nil {
   162  			gmsg.Nack()
   163  		} else {
   164  			gmsg.Ack()
   165  		}
   166  		return nil
   167  	}, nil
   168  }
   169  
   170  // Read attempts to read a new message from the target subscription.
   171  func (c *GCPPubSub) Read() (types.Message, error) {
   172  	c.subMut.Lock()
   173  	msgsChan := c.msgsChan
   174  	c.subMut.Unlock()
   175  	if msgsChan == nil {
   176  		return nil, types.ErrNotConnected
   177  	}
   178  
   179  	msg := message.New(nil)
   180  
   181  	gmsg, open := <-msgsChan
   182  	if !open {
   183  		return nil, types.ErrNotConnected
   184  	}
   185  	c.pendingMsgs = append(c.pendingMsgs, gmsg)
   186  	part := message.NewPart(gmsg.Data)
   187  	part.SetMetadata(metadata.New(gmsg.Attributes))
   188  	part.Metadata().Set("gcp_pubsub_publish_time_unix", strconv.FormatInt(gmsg.PublishTime.Unix(), 10))
   189  	msg.Append(part)
   190  
   191  batchLoop:
   192  	for msg.Len() < c.conf.MaxBatchCount {
   193  		select {
   194  		case gmsg, open = <-msgsChan:
   195  		default:
   196  			// Drained the buffer
   197  			break batchLoop
   198  		}
   199  		if !open {
   200  			return nil, types.ErrNotConnected
   201  		}
   202  		c.pendingMsgs = append(c.pendingMsgs, gmsg)
   203  		part := message.NewPart(gmsg.Data)
   204  		part.SetMetadata(metadata.New(gmsg.Attributes))
   205  		part.Metadata().Set("gcp_pubsub_publish_time_unix", strconv.FormatInt(gmsg.PublishTime.Unix(), 10))
   206  		msg.Append(part)
   207  	}
   208  
   209  	return msg, nil
   210  }
   211  
   212  // Acknowledge confirms whether or not our unacknowledged messages have been
   213  // successfully propagated or not.
   214  func (c *GCPPubSub) Acknowledge(err error) error {
   215  	for _, msg := range c.pendingMsgs {
   216  		if err == nil {
   217  			msg.Ack()
   218  		} else {
   219  			msg.Nack()
   220  		}
   221  	}
   222  	c.pendingMsgs = nil
   223  	return nil
   224  }
   225  
   226  // CloseAsync begins cleaning up resources used by this reader asynchronously.
   227  func (c *GCPPubSub) CloseAsync() {
   228  	c.subMut.Lock()
   229  	if c.closeFunc != nil {
   230  		c.closeFunc()
   231  		c.closeFunc = nil
   232  	}
   233  	c.subMut.Unlock()
   234  }
   235  
   236  // WaitForClose will block until either the reader is closed or a specified
   237  // timeout occurs.
   238  func (c *GCPPubSub) WaitForClose(time.Duration) error {
   239  	return nil
   240  }
   241  
   242  //------------------------------------------------------------------------------