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 }