github.com/thiagoyeds/go-cloud@v0.26.0/pubsub/pubsub.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://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 pubsub provides an easy and portable way to interact with
    16  // publish/subscribe systems. Subpackages contain driver implementations of
    17  // pubsub for supported services
    18  //
    19  // See https://gocloud.dev/howto/pubsub/ for a detailed how-to guide.
    20  //
    21  //
    22  // At-most-once and At-least-once Delivery
    23  //
    24  // The semantics of message delivery vary across PubSub services.
    25  // Some services guarantee that messages received by subscribers but not
    26  // acknowledged are delivered again (at-least-once semantics). In others,
    27  // a message will be delivered only once, if it is delivered at all
    28  // (at-most-once semantics). Some services support both modes via options.
    29  //
    30  // This package accommodates both kinds of systems, but application developers
    31  // should think carefully about which kind of semantics the application needs.
    32  // Even though the application code may look similar, system-level
    33  // characteristics are quite different. See the driver package
    34  // documentation for more information about message delivery semantics.
    35  //
    36  // After receiving a Message via Subscription.Receive:
    37  //  - Always call Message.Ack or Message.Nack after processing the message.
    38  //  - For some drivers, Ack will be a no-op.
    39  //  - For some drivers, Nack is not supported and will panic; you can call
    40  //    Message.Nackable to see.
    41  //
    42  // OpenCensus Integration
    43  //
    44  // OpenCensus supports tracing and metric collection for multiple languages and
    45  // backend providers. See https://opencensus.io.
    46  //
    47  // This API collects OpenCensus traces and metrics for the following methods:
    48  //  - Topic.Send
    49  //  - Topic.Shutdown
    50  //  - Subscription.Receive
    51  //  - Subscription.Shutdown
    52  //  - The internal driver methods SendBatch, SendAcks and ReceiveBatch.
    53  // All trace and metric names begin with the package import path.
    54  // The traces add the method name.
    55  // For example, "gocloud.dev/pubsub/Topic.Send".
    56  // The metrics are "completed_calls", a count of completed method calls by driver,
    57  // method and status (error code); and "latency", a distribution of method latency
    58  // by driver and method.
    59  // For example, "gocloud.dev/pubsub/latency".
    60  //
    61  // To enable trace collection in your application, see "Configure Exporter" at
    62  // https://opencensus.io/quickstart/go/tracing.
    63  // To enable metric collection in your application, see "Exporting stats" at
    64  // https://opencensus.io/quickstart/go/metrics.
    65  package pubsub // import "gocloud.dev/pubsub"
    66  
    67  import (
    68  	"context"
    69  	"fmt"
    70  	"log"
    71  	"math"
    72  	"net/url"
    73  	"reflect"
    74  	"runtime"
    75  	"sync"
    76  	"time"
    77  	"unicode/utf8"
    78  
    79  	"github.com/googleapis/gax-go/v2"
    80  	"gocloud.dev/gcerrors"
    81  	"gocloud.dev/internal/gcerr"
    82  	"gocloud.dev/internal/oc"
    83  	"gocloud.dev/internal/openurl"
    84  	"gocloud.dev/internal/retry"
    85  	"gocloud.dev/pubsub/batcher"
    86  	"gocloud.dev/pubsub/driver"
    87  	"golang.org/x/sync/errgroup"
    88  )
    89  
    90  // Message contains data to be published.
    91  type Message struct {
    92  	// LoggableID will be set to an opaque message identifer for
    93  	// received messages, useful for debug logging. No assumptions should
    94  	// be made about the content.
    95  	LoggableID string
    96  
    97  	// Body contains the content of the message.
    98  	Body []byte
    99  
   100  	// Metadata has key/value metadata for the message.
   101  	//
   102  	// When sending a message, set any key/value pairs you want associated with
   103  	// the message. It is acceptable for Metadata to be nil.
   104  	// Note that some services limit the number of key/value pairs per message.
   105  	//
   106  	// When receiving a message, Metadata will be nil if the message has no
   107  	// associated metadata.
   108  	Metadata map[string]string
   109  
   110  	// BeforeSend is a callback used when sending a message. It will always be
   111  	// set to nil for received messages.
   112  	//
   113  	// The callback will be called exactly once, before the message is sent.
   114  	//
   115  	// asFunc converts its argument to driver-specific types.
   116  	// See https://gocloud.dev/concepts/as/ for background information.
   117  	BeforeSend func(asFunc func(interface{}) bool) error
   118  
   119  	// AfterSend is a callback used when sending a message. It will always be
   120  	// set to nil for received messages.
   121  	//
   122  	// The callback will be called at most once, after the message is sent.
   123  	// If Send returns an error, AfterSend will not be called.
   124  	//
   125  	// asFunc converts its argument to driver-specific types.
   126  	// See https://gocloud.dev/concepts/as/ for background information.
   127  	AfterSend func(asFunc func(interface{}) bool) error
   128  
   129  	// asFunc invokes driver.Message.AsFunc.
   130  	asFunc func(interface{}) bool
   131  
   132  	// ack is a closure that queues this message for the action (ack or nack).
   133  	ack func(isAck bool)
   134  
   135  	// nackable is true iff Nack can be called without panicking.
   136  	nackable bool
   137  
   138  	// mu guards isAcked in case Ack/Nack is called concurrently.
   139  	mu sync.Mutex
   140  
   141  	// isAcked tells whether this message has already had its Ack or Nack
   142  	// method called.
   143  	isAcked bool
   144  }
   145  
   146  // Ack acknowledges the message, telling the server that it does not need to be
   147  // sent again to the associated Subscription. It will be a no-op for some
   148  // drivers; see
   149  // https://godoc.org/gocloud.dev/pubsub#hdr-At_most_once_and_At_least_once_Delivery
   150  // for more info.
   151  //
   152  // Ack returns immediately, but the actual ack is sent in the background, and
   153  // is not guaranteed to succeed. If background acks persistently fail, the error
   154  // will be returned from a subsequent Receive.
   155  func (m *Message) Ack() {
   156  	m.mu.Lock()
   157  	defer m.mu.Unlock()
   158  	if m.isAcked {
   159  		panic(fmt.Sprintf("Ack/Nack called twice on message: %+v", m))
   160  	}
   161  	m.ack(true)
   162  	m.isAcked = true
   163  }
   164  
   165  // Nackable returns true iff Nack can be called without panicking.
   166  //
   167  // Some services do not support Nack; for example, at-most-once services
   168  // can't redeliver a message. See
   169  // https://godoc.org/gocloud.dev/pubsub#hdr-At_most_once_and_At_least_once_Delivery
   170  // for more info.
   171  func (m *Message) Nackable() bool {
   172  	return m.nackable
   173  }
   174  
   175  // Nack (short for negative acknowledgment) tells the server that this Message
   176  // was not processed and should be redelivered.
   177  //
   178  // Nack panics for some drivers, as Nack is meaningless when messages can't be
   179  // redelivered. You can call Nackable to determine if Nack is available. See
   180  // https://godoc.org/gocloud.dev/pubsub#hdr-At_most_once_and_At_least_once_Delivery
   181  // fore more info.
   182  //
   183  // Nack returns immediately, but the actual nack is sent in the background,
   184  // and is not guaranteed to succeed.
   185  //
   186  // Nack is a performance optimization for retrying transient failures. It
   187  // must not be used for message parse errors or other messages that the
   188  // application will never be able to process: calling Nack will cause them to
   189  // be redelivered and overload the server. Instead, an application should call
   190  // Ack and log the failure in some monitored way.
   191  func (m *Message) Nack() {
   192  	m.mu.Lock()
   193  	defer m.mu.Unlock()
   194  	if m.isAcked {
   195  		panic(fmt.Sprintf("Ack/Nack called twice on message: %+v", m))
   196  	}
   197  	if !m.nackable {
   198  		panic("Message.Nack is not supported by this driver")
   199  	}
   200  	m.ack(false)
   201  	m.isAcked = true
   202  }
   203  
   204  // As converts i to driver-specific types.
   205  // See https://gocloud.dev/concepts/as/ for background information, the "As"
   206  // examples in this package for examples, and the driver package
   207  // documentation for the specific types supported for that driver.
   208  // As panics unless it is called on a message obtained from Subscription.Receive.
   209  func (m *Message) As(i interface{}) bool {
   210  	if m.asFunc == nil {
   211  		panic("As called on a Message that was not obtained from Receive")
   212  	}
   213  	return m.asFunc(i)
   214  }
   215  
   216  // Topic publishes messages to all its subscribers.
   217  type Topic struct {
   218  	driver  driver.Topic
   219  	batcher *batcher.Batcher
   220  	tracer  *oc.Tracer
   221  	mu      sync.Mutex
   222  	err     error
   223  
   224  	// cancel cancels all SendBatch calls.
   225  	cancel func()
   226  }
   227  
   228  type msgErrChan struct {
   229  	msg     *Message
   230  	errChan chan error
   231  }
   232  
   233  // Send publishes a message. It only returns after the message has been
   234  // sent, or failed to be sent. Send can be called from multiple goroutines
   235  // at once.
   236  func (t *Topic) Send(ctx context.Context, m *Message) (err error) {
   237  	ctx = t.tracer.Start(ctx, "Topic.Send")
   238  	defer func() { t.tracer.End(ctx, err) }()
   239  
   240  	// Check for doneness before we do any work.
   241  	if err := ctx.Err(); err != nil {
   242  		return err // Return context errors unwrapped.
   243  	}
   244  	t.mu.Lock()
   245  	err = t.err
   246  	t.mu.Unlock()
   247  	if err != nil {
   248  		return err // t.err wrapped when set
   249  	}
   250  	if m.LoggableID != "" {
   251  		return gcerr.Newf(gcerr.InvalidArgument, nil, "pubsub: Message.LoggableID should not be set when sending a message")
   252  	}
   253  	for k, v := range m.Metadata {
   254  		if !utf8.ValidString(k) {
   255  			return gcerr.Newf(gcerr.InvalidArgument, nil, "pubsub: Message.Metadata keys must be valid UTF-8 strings: %q", k)
   256  		}
   257  		if !utf8.ValidString(v) {
   258  			return gcerr.Newf(gcerr.InvalidArgument, nil, "pubsub: Message.Metadata values must be valid UTF-8 strings: %q", v)
   259  		}
   260  	}
   261  	dm := &driver.Message{
   262  		Body:       m.Body,
   263  		Metadata:   m.Metadata,
   264  		BeforeSend: m.BeforeSend,
   265  		AfterSend:  m.AfterSend,
   266  	}
   267  	return t.batcher.Add(ctx, dm)
   268  }
   269  
   270  var errTopicShutdown = gcerr.Newf(gcerr.FailedPrecondition, nil, "pubsub: Topic has been Shutdown")
   271  
   272  // Shutdown flushes pending message sends and disconnects the Topic.
   273  // It only returns after all pending messages have been sent.
   274  func (t *Topic) Shutdown(ctx context.Context) (err error) {
   275  	ctx = t.tracer.Start(ctx, "Topic.Shutdown")
   276  	defer func() { t.tracer.End(ctx, err) }()
   277  
   278  	t.mu.Lock()
   279  	if t.err == errTopicShutdown {
   280  		defer t.mu.Unlock()
   281  		return t.err
   282  	}
   283  	t.err = errTopicShutdown
   284  	t.mu.Unlock()
   285  	c := make(chan struct{})
   286  	go func() {
   287  		defer close(c)
   288  		t.batcher.Shutdown()
   289  	}()
   290  	select {
   291  	case <-ctx.Done():
   292  	case <-c:
   293  	}
   294  	t.cancel()
   295  	if err := t.driver.Close(); err != nil {
   296  		return wrapError(t.driver, err)
   297  	}
   298  	return ctx.Err()
   299  }
   300  
   301  // As converts i to driver-specific types.
   302  // See https://gocloud.dev/concepts/as/ for background information, the "As"
   303  // examples in this package for examples, and the driver package
   304  // documentation for the specific types supported for that driver.
   305  func (t *Topic) As(i interface{}) bool {
   306  	return t.driver.As(i)
   307  }
   308  
   309  // ErrorAs converts err to driver-specific types.
   310  // ErrorAs panics if i is nil or not a pointer.
   311  // ErrorAs returns false if err == nil.
   312  // See https://gocloud.dev/concepts/as/ for background information.
   313  func (t *Topic) ErrorAs(err error, i interface{}) bool {
   314  	return gcerr.ErrorAs(err, i, t.driver.ErrorAs)
   315  }
   316  
   317  // NewTopic is for use by drivers only. Do not use in application code.
   318  var NewTopic = newTopic
   319  
   320  // newSendBatcher creates a batcher for topics, for use with NewTopic.
   321  func newSendBatcher(ctx context.Context, t *Topic, dt driver.Topic, opts *batcher.Options) *batcher.Batcher {
   322  	const maxHandlers = 1
   323  	handler := func(items interface{}) error {
   324  		dms := items.([]*driver.Message)
   325  		err := retry.Call(ctx, gax.Backoff{}, dt.IsRetryable, func() (err error) {
   326  			ctx2 := t.tracer.Start(ctx, "driver.Topic.SendBatch")
   327  			defer func() { t.tracer.End(ctx2, err) }()
   328  			return dt.SendBatch(ctx2, dms)
   329  		})
   330  		if err != nil {
   331  			return wrapError(dt, err)
   332  		}
   333  		return nil
   334  	}
   335  	return batcher.New(reflect.TypeOf(&driver.Message{}), opts, handler)
   336  }
   337  
   338  // newTopic makes a pubsub.Topic from a driver.Topic.
   339  //
   340  // opts may be nil to accept defaults.
   341  func newTopic(d driver.Topic, opts *batcher.Options) *Topic {
   342  	ctx, cancel := context.WithCancel(context.Background())
   343  	t := &Topic{
   344  		driver: d,
   345  		tracer: newTracer(d),
   346  		cancel: cancel,
   347  	}
   348  	t.batcher = newSendBatcher(ctx, t, d, opts)
   349  	return t
   350  }
   351  
   352  const pkgName = "gocloud.dev/pubsub"
   353  
   354  var (
   355  	latencyMeasure = oc.LatencyMeasure(pkgName)
   356  
   357  	// OpenCensusViews are predefined views for OpenCensus metrics.
   358  	// The views include counts and latency distributions for API method calls.
   359  	// See the example at https://godoc.org/go.opencensus.io/stats/view for usage.
   360  	OpenCensusViews = oc.Views(pkgName, latencyMeasure)
   361  )
   362  
   363  func newTracer(driver interface{}) *oc.Tracer {
   364  	return &oc.Tracer{
   365  		Package:        pkgName,
   366  		Provider:       oc.ProviderName(driver),
   367  		LatencyMeasure: latencyMeasure,
   368  	}
   369  }
   370  
   371  // Subscription receives published messages.
   372  type Subscription struct {
   373  	driver driver.Subscription
   374  	tracer *oc.Tracer
   375  	// ackBatcher makes batches of acks and nacks and sends them to the server.
   376  	ackBatcher    *batcher.Batcher
   377  	canNack       bool            // true iff the driver supports Nack
   378  	backgroundCtx context.Context // for background SendAcks and ReceiveBatch calls
   379  	cancel        func()          // for canceling backgroundCtx
   380  
   381  	recvBatchOpts *batcher.Options
   382  
   383  	mu               sync.Mutex        // protects everything below
   384  	q                []*driver.Message // local queue of messages downloaded from server
   385  	err              error             // permanent error
   386  	unreportedAckErr error             // permanent error from background SendAcks that hasn't been returned to the user yet
   387  	waitc            chan struct{}     // for goroutines waiting on ReceiveBatch
   388  	runningBatchSize float64           // running number of messages to request via ReceiveBatch
   389  	throughputStart  time.Time         // start time for throughput measurement, or the zero Time if queue is empty
   390  	throughputEnd    time.Time         // end time for throughput measurement, or the zero Time if queue is not empty
   391  	throughputCount  int               // number of msgs given out via Receive since throughputStart
   392  
   393  	// Used in tests.
   394  	preReceiveBatchHook func(maxMessages int)
   395  }
   396  
   397  const (
   398  	// The desired duration of a subscription's queue of messages (the messages pulled
   399  	// and waiting in memory to be doled out to Receive callers). This is how long
   400  	// it would take to drain the queue at the current processing rate.
   401  	// The relationship to queue length (number of messages) is
   402  	//
   403  	//      lengthInMessages = desiredQueueDuration / averageProcessTimePerMessage
   404  	//
   405  	// In other words, if it takes 100ms to process a message on average, and we want
   406  	// 2s worth of queued messages, then we need 2/.1 = 20 messages in the queue.
   407  	//
   408  	// If desiredQueueDuration is too small, then there won't be a large enough buffer
   409  	// of messages to handle fluctuations in processing time, and the queue is likely
   410  	// to become empty, reducing throughput. If desiredQueueDuration is too large, then
   411  	// messages will wait in memory for a long time, possibly timing out (that is,
   412  	// their ack deadline will be exceeded). Those messages could have been handled
   413  	// by another process receiving from the same subscription.
   414  	desiredQueueDuration = 2 * time.Second
   415  
   416  	// Expected duration of calls to driver.ReceiveBatch, at some high percentile.
   417  	// We'll try to fetch more messages when the current queue is predicted
   418  	// to be used up in expectedReceiveBatchDuration.
   419  	expectedReceiveBatchDuration = 1 * time.Second
   420  
   421  	// s.runningBatchSize holds our current best guess for how many messages to
   422  	// fetch in order to have a buffer of desiredQueueDuration. When we have
   423  	// fewer than prefetchRatio * s.runningBatchSize messages left, that means
   424  	// we expect to run out of messages in expectedReceiveBatchDuration, so we
   425  	// should initiate another ReceiveBatch call.
   426  	prefetchRatio = float64(expectedReceiveBatchDuration) / float64(desiredQueueDuration)
   427  
   428  	// The initial # of messages to request via ReceiveBatch.
   429  	initialBatchSize = 1
   430  
   431  	// The factor by which old batch sizes decay when a new value is added to the
   432  	// running value. The larger this number, the more weight will be given to the
   433  	// newest value in preference to older ones.
   434  	//
   435  	// The delta based on a single value is capped by the constants below.
   436  	decay = 0.5
   437  
   438  	// The maximum growth factor in a single jump. Higher values mean that the
   439  	// batch size can increase more aggressively. For example, 2.0 means that the
   440  	// batch size will at most double from one ReceiveBatch call to the next.
   441  	maxGrowthFactor = 2.0
   442  
   443  	// Similarly, the maximum shrink factor. Lower values mean that the batch size
   444  	// can shrink more aggressively. For example; 0.75 means that the batch size
   445  	// will at most shrink to 75% of what it was before. Note that values less
   446  	// than (1-decay) will have no effect because the running value can't change
   447  	// by more than that.
   448  	maxShrinkFactor = 0.75
   449  
   450  	// The maximum batch size to request. Setting this too low doesn't allow
   451  	// drivers to get lots of messages at once; setting it too small risks having
   452  	// drivers spend a long time in ReceiveBatch trying to achieve it.
   453  	maxBatchSize = 3000
   454  )
   455  
   456  // updateBatchSize updates the number of messages to request in ReceiveBatch
   457  // based on the previous batch size and the rate of messages being pulled from
   458  // the queue, measured using s.throughput*.
   459  //
   460  // It returns the number of messages to request in this ReceiveBatch call.
   461  //
   462  // s.mu must be held.
   463  func (s *Subscription) updateBatchSize() int {
   464  	// If we're always only doing one at a time, there's no point in this.
   465  	if s.recvBatchOpts != nil && s.recvBatchOpts.MaxBatchSize == 1 && s.recvBatchOpts.MaxHandlers == 1 {
   466  		return 1
   467  	}
   468  	now := time.Now()
   469  	if s.throughputStart.IsZero() {
   470  		// No throughput measurement; don't update s.runningBatchSize.
   471  	} else {
   472  		// Update s.runningBatchSize based on throughput since our last time here,
   473  		// as measured by the ratio of the number of messages returned to elapsed
   474  		// time when there were messages available in the queue.
   475  		if s.throughputEnd.IsZero() {
   476  			s.throughputEnd = now
   477  		}
   478  		elapsed := s.throughputEnd.Sub(s.throughputStart)
   479  		if elapsed == 0 {
   480  			// Avoid divide-by-zero.
   481  			elapsed = 1 * time.Millisecond
   482  		}
   483  		msgsPerSec := float64(s.throughputCount) / elapsed.Seconds()
   484  
   485  		// The "ideal" batch size is how many messages we'd need in the queue to
   486  		// support desiredQueueDuration at the msgsPerSec rate.
   487  		idealBatchSize := desiredQueueDuration.Seconds() * msgsPerSec
   488  
   489  		// Move s.runningBatchSize towards the ideal.
   490  		// We first combine the previous value and the new value, with weighting
   491  		// based on decay, and then cap the growth/shrinkage.
   492  		newBatchSize := s.runningBatchSize*(1-decay) + idealBatchSize*decay
   493  		if max := s.runningBatchSize * maxGrowthFactor; newBatchSize > max {
   494  			s.runningBatchSize = max
   495  		} else if min := s.runningBatchSize * maxShrinkFactor; newBatchSize < min {
   496  			s.runningBatchSize = min
   497  		} else {
   498  			s.runningBatchSize = newBatchSize
   499  		}
   500  	}
   501  
   502  	// Reset throughput measurement markers.
   503  	if len(s.q) > 0 {
   504  		s.throughputStart = now
   505  	} else {
   506  		// Will get set to non-zero value when we receive some messages.
   507  		s.throughputStart = time.Time{}
   508  	}
   509  	s.throughputEnd = time.Time{}
   510  	s.throughputCount = 0
   511  
   512  	// Using Ceil guarantees at least one message.
   513  	return int(math.Ceil(math.Min(s.runningBatchSize, maxBatchSize)))
   514  }
   515  
   516  // Receive receives and returns the next message from the Subscription's queue,
   517  // blocking and polling if none are available. It can be called
   518  // concurrently from multiple goroutines.
   519  //
   520  // Receive retries retryable errors from the underlying driver forever.
   521  // Therefore, if Receive returns an error, either:
   522  // 1. It is a non-retryable error from the underlying driver, either from
   523  //    an attempt to fetch more messages or from an attempt to ack messages.
   524  //    Operator intervention may be required (e.g., invalid resource, quota
   525  //    error, etc.). Receive will return the same error from then on, so the
   526  //    application should log the error and either recreate the Subscription,
   527  //    or exit.
   528  // 2. The provided ctx is Done. Error() on the returned error will include both
   529  //    the ctx error and the underlying driver error, and ErrorAs on it
   530  //    can access the underlying driver error type if needed. Receive may
   531  //    be called again with a fresh ctx.
   532  //
   533  // Callers can distinguish between the two by checking if the ctx they passed
   534  // is Done, or via xerrors.Is(err, context.DeadlineExceeded or context.Canceled)
   535  // on the returned error.
   536  //
   537  // The Ack method of the returned Message must be called once the message has
   538  // been processed, to prevent it from being received again.
   539  func (s *Subscription) Receive(ctx context.Context) (_ *Message, err error) {
   540  	ctx = s.tracer.Start(ctx, "Subscription.Receive")
   541  	defer func() { s.tracer.End(ctx, err) }()
   542  
   543  	s.mu.Lock()
   544  	defer s.mu.Unlock()
   545  	for {
   546  		// The lock is always held here, at the top of the loop.
   547  		if s.err != nil {
   548  			// The Subscription is in a permanent error state. Return the error.
   549  			s.unreportedAckErr = nil
   550  			return nil, s.err // s.err wrapped when set
   551  		}
   552  
   553  		// Short circuit if ctx is Done.
   554  		// Otherwise, we'll continue to return messages from the queue, and even
   555  		// get new messages if driver.ReceiveBatch doesn't return an error when
   556  		// ctx is done.
   557  		if err := ctx.Err(); err != nil {
   558  			return nil, err
   559  		}
   560  
   561  		if s.waitc == nil && float64(len(s.q)) <= s.runningBatchSize*prefetchRatio {
   562  			// We think we're going to run out of messages in expectedReceiveBatchDuration,
   563  			// and there's no outstanding ReceiveBatch call, so initiate one in the
   564  			// background.
   565  			// Completion will be signalled to this goroutine, and to any other
   566  			// waiting goroutines, by closing s.waitc.
   567  			s.waitc = make(chan struct{})
   568  			batchSize := s.updateBatchSize()
   569  
   570  			go func() {
   571  				if s.preReceiveBatchHook != nil {
   572  					s.preReceiveBatchHook(batchSize)
   573  				}
   574  				msgs, err := s.getNextBatch(batchSize)
   575  				s.mu.Lock()
   576  				defer s.mu.Unlock()
   577  				if err != nil {
   578  					// Non-retryable error from ReceiveBatch -> permanent error.
   579  					s.err = err
   580  				} else if len(msgs) > 0 {
   581  					s.q = append(s.q, msgs...)
   582  				}
   583  				// Set the start time for measuring throughput even if we didn't get
   584  				// any messages; this allows batch size to decay over time if there
   585  				// aren't any message available.
   586  				if s.throughputStart.IsZero() {
   587  					s.throughputStart = time.Now()
   588  				}
   589  				close(s.waitc)
   590  				s.waitc = nil
   591  			}()
   592  		}
   593  		if len(s.q) > 0 {
   594  			// At least one message is available. Return it.
   595  			m := s.q[0]
   596  			s.q = s.q[1:]
   597  			s.throughputCount++
   598  
   599  			// Convert driver.Message to Message.
   600  			id := m.AckID
   601  			md := m.Metadata
   602  			if len(md) == 0 {
   603  				md = nil
   604  			}
   605  			loggableID := m.LoggableID
   606  			if loggableID == "" {
   607  				// This shouldn't happen, but just in case it's better to be explicit.
   608  				loggableID = "unknown"
   609  			}
   610  			m2 := &Message{
   611  				LoggableID: loggableID,
   612  				Body:       m.Body,
   613  				Metadata:   md,
   614  				asFunc:     m.AsFunc,
   615  				nackable:   s.canNack,
   616  			}
   617  			m2.ack = func(isAck bool) {
   618  				// Ignore the error channel. Errors are dealt with
   619  				// in the ackBatcher handler.
   620  				_ = s.ackBatcher.AddNoWait(&driver.AckInfo{AckID: id, IsAck: isAck})
   621  			}
   622  			// Add a finalizer that complains if the Message we return isn't
   623  			// acked or nacked.
   624  			_, file, lineno, ok := runtime.Caller(1) // the caller of Receive
   625  			runtime.SetFinalizer(m2, func(m *Message) {
   626  				m.mu.Lock()
   627  				defer m.mu.Unlock()
   628  				if !m.isAcked {
   629  					var caller string
   630  					if ok {
   631  						caller = fmt.Sprintf(" (%s:%d)", file, lineno)
   632  					}
   633  					log.Printf("A pubsub.Message was never Acked or Nacked%s", caller)
   634  				}
   635  			})
   636  			return m2, nil
   637  		}
   638  		// No messages are available. Close the interval for throughput measurement.
   639  		if s.throughputEnd.IsZero() && !s.throughputStart.IsZero() && s.throughputCount > 0 {
   640  			s.throughputEnd = time.Now()
   641  		}
   642  		// A call to ReceiveBatch must be in flight. Wait for it.
   643  		waitc := s.waitc
   644  		s.mu.Unlock()
   645  		select {
   646  		case <-waitc:
   647  			s.mu.Lock()
   648  			// Continue to top of loop.
   649  		case <-ctx.Done():
   650  			s.mu.Lock()
   651  			return nil, ctx.Err()
   652  		}
   653  	}
   654  }
   655  
   656  // getNextBatch gets the next batch of messages from the server and returns it.
   657  func (s *Subscription) getNextBatch(nMessages int) ([]*driver.Message, error) {
   658  	var mu sync.Mutex
   659  	var q []*driver.Message
   660  
   661  	// Split nMessages into batches based on recvBatchOpts; we'll make a
   662  	// separate ReceiveBatch call for each batch, and aggregate the results in
   663  	// msgs.
   664  	batches := batcher.Split(nMessages, s.recvBatchOpts)
   665  
   666  	g, ctx := errgroup.WithContext(s.backgroundCtx)
   667  	for _, maxMessagesInBatch := range batches {
   668  		// Make a copy of the loop variable since it will be used by a goroutine.
   669  		curMaxMessagesInBatch := maxMessagesInBatch
   670  		g.Go(func() error {
   671  			var msgs []*driver.Message
   672  			err := retry.Call(ctx, gax.Backoff{}, s.driver.IsRetryable, func() error {
   673  				var err error
   674  				ctx2 := s.tracer.Start(ctx, "driver.Subscription.ReceiveBatch")
   675  				defer func() { s.tracer.End(ctx2, err) }()
   676  				msgs, err = s.driver.ReceiveBatch(ctx2, curMaxMessagesInBatch)
   677  				return err
   678  			})
   679  			if err != nil {
   680  				return wrapError(s.driver, err)
   681  			}
   682  			mu.Lock()
   683  			defer mu.Unlock()
   684  			q = append(q, msgs...)
   685  			return nil
   686  		})
   687  	}
   688  	if err := g.Wait(); err != nil {
   689  		return nil, err
   690  	}
   691  	return q, nil
   692  }
   693  
   694  var errSubscriptionShutdown = gcerr.Newf(gcerr.FailedPrecondition, nil, "pubsub: Subscription has been Shutdown")
   695  
   696  // Shutdown flushes pending ack sends and disconnects the Subscription.
   697  func (s *Subscription) Shutdown(ctx context.Context) (err error) {
   698  	ctx = s.tracer.Start(ctx, "Subscription.Shutdown")
   699  	defer func() { s.tracer.End(ctx, err) }()
   700  
   701  	s.mu.Lock()
   702  	if s.err == errSubscriptionShutdown {
   703  		// Already Shutdown.
   704  		defer s.mu.Unlock()
   705  		return s.err
   706  	}
   707  	s.err = errSubscriptionShutdown
   708  	s.mu.Unlock()
   709  	c := make(chan struct{})
   710  	go func() {
   711  		defer close(c)
   712  		if s.ackBatcher != nil {
   713  			s.ackBatcher.Shutdown()
   714  		}
   715  	}()
   716  	select {
   717  	case <-ctx.Done():
   718  	case <-c:
   719  	}
   720  	s.cancel()
   721  	if err := s.driver.Close(); err != nil {
   722  		return wrapError(s.driver, err)
   723  	}
   724  	s.mu.Lock()
   725  	defer s.mu.Unlock()
   726  	if err := s.unreportedAckErr; err != nil {
   727  		s.unreportedAckErr = nil
   728  		return err
   729  	}
   730  	return ctx.Err()
   731  }
   732  
   733  // As converts i to driver-specific types.
   734  // See https://gocloud.dev/concepts/as/ for background information, the "As"
   735  // examples in this package for examples, and the driver package
   736  // documentation for the specific types supported for that driver.
   737  func (s *Subscription) As(i interface{}) bool {
   738  	return s.driver.As(i)
   739  }
   740  
   741  // ErrorAs converts err to driver-specific types.
   742  // ErrorAs panics if i is nil or not a pointer.
   743  // ErrorAs returns false if err == nil.
   744  // See Topic.As for more details.
   745  func (s *Subscription) ErrorAs(err error, i interface{}) bool {
   746  	return gcerr.ErrorAs(err, i, s.driver.ErrorAs)
   747  }
   748  
   749  // NewSubscription is for use by drivers only. Do not use in application code.
   750  var NewSubscription = newSubscription
   751  
   752  // newSubscription creates a Subscription from a driver.Subscription.
   753  //
   754  // recvBatchOpts sets options for Receive batching. May be nil to accept
   755  // defaults. The ideal number of messages to receive at a time is determined
   756  // dynamically, then split into multiple possibly concurrent calls to
   757  // driver.ReceiveBatch based on recvBatchOptions.
   758  //
   759  // ackBatcherOpts sets options for ack+nack batching. May be nil to accept
   760  // defaults.
   761  func newSubscription(ds driver.Subscription, recvBatchOpts, ackBatcherOpts *batcher.Options) *Subscription {
   762  	ctx, cancel := context.WithCancel(context.Background())
   763  	s := &Subscription{
   764  		driver:           ds,
   765  		tracer:           newTracer(ds),
   766  		cancel:           cancel,
   767  		backgroundCtx:    ctx,
   768  		recvBatchOpts:    recvBatchOpts,
   769  		runningBatchSize: initialBatchSize,
   770  		canNack:          ds.CanNack(),
   771  	}
   772  	s.ackBatcher = newAckBatcher(ctx, s, ds, ackBatcherOpts)
   773  	return s
   774  }
   775  
   776  func newAckBatcher(ctx context.Context, s *Subscription, ds driver.Subscription, opts *batcher.Options) *batcher.Batcher {
   777  	const maxHandlers = 1
   778  	handler := func(items interface{}) error {
   779  		var acks, nacks []driver.AckID
   780  		for _, a := range items.([]*driver.AckInfo) {
   781  			if a.IsAck {
   782  				acks = append(acks, a.AckID)
   783  			} else {
   784  				nacks = append(nacks, a.AckID)
   785  			}
   786  		}
   787  		g, ctx := errgroup.WithContext(ctx)
   788  		if len(acks) > 0 {
   789  			g.Go(func() error {
   790  				return retry.Call(ctx, gax.Backoff{}, ds.IsRetryable, func() (err error) {
   791  					ctx2 := s.tracer.Start(ctx, "driver.Subscription.SendAcks")
   792  					defer func() { s.tracer.End(ctx2, err) }()
   793  					return ds.SendAcks(ctx2, acks)
   794  				})
   795  			})
   796  		}
   797  		if len(nacks) > 0 {
   798  			g.Go(func() error {
   799  				return retry.Call(ctx, gax.Backoff{}, ds.IsRetryable, func() (err error) {
   800  					ctx2 := s.tracer.Start(ctx, "driver.Subscription.SendNacks")
   801  					defer func() { s.tracer.End(ctx2, err) }()
   802  					return ds.SendNacks(ctx2, nacks)
   803  				})
   804  			})
   805  		}
   806  		err := g.Wait()
   807  		// Remember a non-retryable error from SendAcks/Nacks. It will be returned on the
   808  		// next call to Receive.
   809  		if err != nil {
   810  			err = wrapError(s.driver, err)
   811  			s.mu.Lock()
   812  			s.err = err
   813  			s.unreportedAckErr = err
   814  			s.mu.Unlock()
   815  		}
   816  		return err
   817  	}
   818  	return batcher.New(reflect.TypeOf([]*driver.AckInfo{}).Elem(), opts, handler)
   819  }
   820  
   821  type errorCoder interface {
   822  	ErrorCode(error) gcerrors.ErrorCode
   823  }
   824  
   825  func wrapError(ec errorCoder, err error) error {
   826  	if err == nil {
   827  		return nil
   828  	}
   829  	if gcerr.DoNotWrap(err) {
   830  		return err
   831  	}
   832  	return gcerr.New(ec.ErrorCode(err), err, 2, "pubsub")
   833  }
   834  
   835  // TopicURLOpener represents types than can open Topics based on a URL.
   836  // The opener must not modify the URL argument. OpenTopicURL must be safe to
   837  // call from multiple goroutines.
   838  //
   839  // This interface is generally implemented by types in driver packages.
   840  type TopicURLOpener interface {
   841  	OpenTopicURL(ctx context.Context, u *url.URL) (*Topic, error)
   842  }
   843  
   844  // SubscriptionURLOpener represents types than can open Subscriptions based on a URL.
   845  // The opener must not modify the URL argument. OpenSubscriptionURL must be safe to
   846  // call from multiple goroutines.
   847  //
   848  // This interface is generally implemented by types in driver packages.
   849  type SubscriptionURLOpener interface {
   850  	OpenSubscriptionURL(ctx context.Context, u *url.URL) (*Subscription, error)
   851  }
   852  
   853  // URLMux is a URL opener multiplexer. It matches the scheme of the URLs
   854  // against a set of registered schemes and calls the opener that matches the
   855  // URL's scheme.
   856  // See https://gocloud.dev/concepts/urls/ for more information.
   857  //
   858  // The zero value is a multiplexer with no registered schemes.
   859  type URLMux struct {
   860  	subscriptionSchemes openurl.SchemeMap
   861  	topicSchemes        openurl.SchemeMap
   862  }
   863  
   864  // TopicSchemes returns a sorted slice of the registered Topic schemes.
   865  func (mux *URLMux) TopicSchemes() []string { return mux.topicSchemes.Schemes() }
   866  
   867  // ValidTopicScheme returns true iff scheme has been registered for Topics.
   868  func (mux *URLMux) ValidTopicScheme(scheme string) bool { return mux.topicSchemes.ValidScheme(scheme) }
   869  
   870  // SubscriptionSchemes returns a sorted slice of the registered Subscription schemes.
   871  func (mux *URLMux) SubscriptionSchemes() []string { return mux.subscriptionSchemes.Schemes() }
   872  
   873  // ValidSubscriptionScheme returns true iff scheme has been registered for Subscriptions.
   874  func (mux *URLMux) ValidSubscriptionScheme(scheme string) bool {
   875  	return mux.subscriptionSchemes.ValidScheme(scheme)
   876  }
   877  
   878  // RegisterTopic registers the opener with the given scheme. If an opener
   879  // already exists for the scheme, RegisterTopic panics.
   880  func (mux *URLMux) RegisterTopic(scheme string, opener TopicURLOpener) {
   881  	mux.topicSchemes.Register("pubsub", "Topic", scheme, opener)
   882  }
   883  
   884  // RegisterSubscription registers the opener with the given scheme. If an opener
   885  // already exists for the scheme, RegisterSubscription panics.
   886  func (mux *URLMux) RegisterSubscription(scheme string, opener SubscriptionURLOpener) {
   887  	mux.subscriptionSchemes.Register("pubsub", "Subscription", scheme, opener)
   888  }
   889  
   890  // OpenTopic calls OpenTopicURL with the URL parsed from urlstr.
   891  // OpenTopic is safe to call from multiple goroutines.
   892  func (mux *URLMux) OpenTopic(ctx context.Context, urlstr string) (*Topic, error) {
   893  	opener, u, err := mux.topicSchemes.FromString("Topic", urlstr)
   894  	if err != nil {
   895  		return nil, err
   896  	}
   897  	return opener.(TopicURLOpener).OpenTopicURL(ctx, u)
   898  }
   899  
   900  // OpenSubscription calls OpenSubscriptionURL with the URL parsed from urlstr.
   901  // OpenSubscription is safe to call from multiple goroutines.
   902  func (mux *URLMux) OpenSubscription(ctx context.Context, urlstr string) (*Subscription, error) {
   903  	opener, u, err := mux.subscriptionSchemes.FromString("Subscription", urlstr)
   904  	if err != nil {
   905  		return nil, err
   906  	}
   907  	return opener.(SubscriptionURLOpener).OpenSubscriptionURL(ctx, u)
   908  }
   909  
   910  // OpenTopicURL dispatches the URL to the opener that is registered with the
   911  // URL's scheme. OpenTopicURL is safe to call from multiple goroutines.
   912  func (mux *URLMux) OpenTopicURL(ctx context.Context, u *url.URL) (*Topic, error) {
   913  	opener, err := mux.topicSchemes.FromURL("Topic", u)
   914  	if err != nil {
   915  		return nil, err
   916  	}
   917  	return opener.(TopicURLOpener).OpenTopicURL(ctx, u)
   918  }
   919  
   920  // OpenSubscriptionURL dispatches the URL to the opener that is registered with the
   921  // URL's scheme. OpenSubscriptionURL is safe to call from multiple goroutines.
   922  func (mux *URLMux) OpenSubscriptionURL(ctx context.Context, u *url.URL) (*Subscription, error) {
   923  	opener, err := mux.subscriptionSchemes.FromURL("Subscription", u)
   924  	if err != nil {
   925  		return nil, err
   926  	}
   927  	return opener.(SubscriptionURLOpener).OpenSubscriptionURL(ctx, u)
   928  }
   929  
   930  var defaultURLMux = &URLMux{}
   931  
   932  // DefaultURLMux returns the URLMux used by OpenTopic and OpenSubscription.
   933  //
   934  // Driver packages can use this to register their TopicURLOpener and/or
   935  // SubscriptionURLOpener on the mux.
   936  func DefaultURLMux() *URLMux {
   937  	return defaultURLMux
   938  }
   939  
   940  // OpenTopic opens the Topic identified by the URL given.
   941  // See the URLOpener documentation in driver subpackages for
   942  // details on supported URL formats, and https://gocloud.dev/concepts/urls
   943  // for more information.
   944  func OpenTopic(ctx context.Context, urlstr string) (*Topic, error) {
   945  	return defaultURLMux.OpenTopic(ctx, urlstr)
   946  }
   947  
   948  // OpenSubscription opens the Subscription identified by the URL given.
   949  // See the URLOpener documentation in driver subpackages for
   950  // details on supported URL formats, and https://gocloud.dev/concepts/urls
   951  // for more information.
   952  func OpenSubscription(ctx context.Context, urlstr string) (*Subscription, error) {
   953  	return defaultURLMux.OpenSubscription(ctx, urlstr)
   954  }