Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

378 rader
11 KiB

  1. // Copyright 2016 Google LLC
  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. // http://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. package pubsub
  15. import (
  16. "errors"
  17. "fmt"
  18. "runtime"
  19. "strings"
  20. "sync"
  21. "time"
  22. "cloud.google.com/go/iam"
  23. "github.com/golang/protobuf/proto"
  24. gax "github.com/googleapis/gax-go"
  25. "golang.org/x/net/context"
  26. "google.golang.org/api/support/bundler"
  27. pb "google.golang.org/genproto/googleapis/pubsub/v1"
  28. "google.golang.org/grpc"
  29. "google.golang.org/grpc/codes"
  30. )
  31. const (
  32. // The maximum number of messages that can be in a single publish request, as
  33. // determined by the PubSub service.
  34. MaxPublishRequestCount = 1000
  35. // The maximum size of a single publish request in bytes, as determined by the PubSub service.
  36. MaxPublishRequestBytes = 1e7
  37. maxInt = int(^uint(0) >> 1)
  38. )
  39. // ErrOversizedMessage indicates that a message's size exceeds MaxPublishRequestBytes.
  40. var ErrOversizedMessage = bundler.ErrOversizedItem
  41. // Topic is a reference to a PubSub topic.
  42. //
  43. // The methods of Topic are safe for use by multiple goroutines.
  44. type Topic struct {
  45. c *Client
  46. // The fully qualified identifier for the topic, in the format "projects/<projid>/topics/<name>"
  47. name string
  48. // Settings for publishing messages. All changes must be made before the
  49. // first call to Publish. The default is DefaultPublishSettings.
  50. PublishSettings PublishSettings
  51. mu sync.RWMutex
  52. stopped bool
  53. bundler *bundler.Bundler
  54. wg sync.WaitGroup
  55. }
  56. // PublishSettings control the bundling of published messages.
  57. type PublishSettings struct {
  58. // Publish a non-empty batch after this delay has passed.
  59. DelayThreshold time.Duration
  60. // Publish a batch when it has this many messages. The maximum is
  61. // MaxPublishRequestCount.
  62. CountThreshold int
  63. // Publish a batch when its size in bytes reaches this value.
  64. ByteThreshold int
  65. // The number of goroutines that invoke the Publish RPC concurrently.
  66. // Defaults to a multiple of GOMAXPROCS.
  67. NumGoroutines int
  68. // The maximum time that the client will attempt to publish a bundle of messages.
  69. Timeout time.Duration
  70. }
  71. // DefaultPublishSettings holds the default values for topics' PublishSettings.
  72. var DefaultPublishSettings = PublishSettings{
  73. DelayThreshold: 1 * time.Millisecond,
  74. CountThreshold: 100,
  75. ByteThreshold: 1e6,
  76. Timeout: 60 * time.Second,
  77. }
  78. // CreateTopic creates a new topic.
  79. // The specified topic ID must start with a letter, and contain only letters
  80. // ([A-Za-z]), numbers ([0-9]), dashes (-), underscores (_), periods (.),
  81. // tildes (~), plus (+) or percent signs (%). It must be between 3 and 255
  82. // characters in length, and must not start with "goog".
  83. // If the topic already exists an error will be returned.
  84. func (c *Client) CreateTopic(ctx context.Context, id string) (*Topic, error) {
  85. t := c.Topic(id)
  86. _, err := c.pubc.CreateTopic(ctx, &pb.Topic{Name: t.name})
  87. if err != nil {
  88. return nil, err
  89. }
  90. return t, nil
  91. }
  92. // Topic creates a reference to a topic in the client's project.
  93. //
  94. // If a Topic's Publish method is called, it has background goroutines
  95. // associated with it. Clean them up by calling Topic.Stop.
  96. //
  97. // Avoid creating many Topic instances if you use them to publish.
  98. func (c *Client) Topic(id string) *Topic {
  99. return c.TopicInProject(id, c.projectID)
  100. }
  101. // TopicInProject creates a reference to a topic in the given project.
  102. //
  103. // If a Topic's Publish method is called, it has background goroutines
  104. // associated with it. Clean them up by calling Topic.Stop.
  105. //
  106. // Avoid creating many Topic instances if you use them to publish.
  107. func (c *Client) TopicInProject(id, projectID string) *Topic {
  108. return newTopic(c, fmt.Sprintf("projects/%s/topics/%s", projectID, id))
  109. }
  110. func newTopic(c *Client, name string) *Topic {
  111. return &Topic{
  112. c: c,
  113. name: name,
  114. PublishSettings: DefaultPublishSettings,
  115. }
  116. }
  117. // Topics returns an iterator which returns all of the topics for the client's project.
  118. func (c *Client) Topics(ctx context.Context) *TopicIterator {
  119. it := c.pubc.ListTopics(ctx, &pb.ListTopicsRequest{Project: c.fullyQualifiedProjectName()})
  120. return &TopicIterator{
  121. c: c,
  122. next: func() (string, error) {
  123. topic, err := it.Next()
  124. if err != nil {
  125. return "", err
  126. }
  127. return topic.Name, nil
  128. },
  129. }
  130. }
  131. // TopicIterator is an iterator that returns a series of topics.
  132. type TopicIterator struct {
  133. c *Client
  134. next func() (string, error)
  135. }
  136. // Next returns the next topic. If there are no more topics, iterator.Done will be returned.
  137. func (tps *TopicIterator) Next() (*Topic, error) {
  138. topicName, err := tps.next()
  139. if err != nil {
  140. return nil, err
  141. }
  142. return newTopic(tps.c, topicName), nil
  143. }
  144. // ID returns the unique idenfier of the topic within its project.
  145. func (t *Topic) ID() string {
  146. slash := strings.LastIndex(t.name, "/")
  147. if slash == -1 {
  148. // name is not a fully-qualified name.
  149. panic("bad topic name")
  150. }
  151. return t.name[slash+1:]
  152. }
  153. // String returns the printable globally unique name for the topic.
  154. func (t *Topic) String() string {
  155. return t.name
  156. }
  157. // Delete deletes the topic.
  158. func (t *Topic) Delete(ctx context.Context) error {
  159. return t.c.pubc.DeleteTopic(ctx, &pb.DeleteTopicRequest{Topic: t.name})
  160. }
  161. // Exists reports whether the topic exists on the server.
  162. func (t *Topic) Exists(ctx context.Context) (bool, error) {
  163. if t.name == "_deleted-topic_" {
  164. return false, nil
  165. }
  166. _, err := t.c.pubc.GetTopic(ctx, &pb.GetTopicRequest{Topic: t.name})
  167. if err == nil {
  168. return true, nil
  169. }
  170. if grpc.Code(err) == codes.NotFound {
  171. return false, nil
  172. }
  173. return false, err
  174. }
  175. func (t *Topic) IAM() *iam.Handle {
  176. return iam.InternalNewHandle(t.c.pubc.Connection(), t.name)
  177. }
  178. // Subscriptions returns an iterator which returns the subscriptions for this topic.
  179. //
  180. // Some of the returned subscriptions may belong to a project other than t.
  181. func (t *Topic) Subscriptions(ctx context.Context) *SubscriptionIterator {
  182. it := t.c.pubc.ListTopicSubscriptions(ctx, &pb.ListTopicSubscriptionsRequest{
  183. Topic: t.name,
  184. })
  185. return &SubscriptionIterator{
  186. c: t.c,
  187. next: it.Next,
  188. }
  189. }
  190. var errTopicStopped = errors.New("pubsub: Stop has been called for this topic")
  191. // Publish publishes msg to the topic asynchronously. Messages are batched and
  192. // sent according to the topic's PublishSettings. Publish never blocks.
  193. //
  194. // Publish returns a non-nil PublishResult which will be ready when the
  195. // message has been sent (or has failed to be sent) to the server.
  196. //
  197. // Publish creates goroutines for batching and sending messages. These goroutines
  198. // need to be stopped by calling t.Stop(). Once stopped, future calls to Publish
  199. // will immediately return a PublishResult with an error.
  200. func (t *Topic) Publish(ctx context.Context, msg *Message) *PublishResult {
  201. // TODO(jba): if this turns out to take significant time, try to approximate it.
  202. // Or, convert the messages to protos in Publish, instead of in the service.
  203. msg.size = proto.Size(&pb.PubsubMessage{
  204. Data: msg.Data,
  205. Attributes: msg.Attributes,
  206. })
  207. r := &PublishResult{ready: make(chan struct{})}
  208. t.initBundler()
  209. t.mu.RLock()
  210. defer t.mu.RUnlock()
  211. // TODO(aboulhosn) [from bcmills] consider changing the semantics of bundler to perform this logic so we don't have to do it here
  212. if t.stopped {
  213. r.set("", errTopicStopped)
  214. return r
  215. }
  216. // TODO(jba) [from bcmills] consider using a shared channel per bundle
  217. // (requires Bundler API changes; would reduce allocations)
  218. // The call to Add should never return an error because the bundler's
  219. // BufferedByteLimit is set to maxInt; we do not perform any flow
  220. // control in the client.
  221. err := t.bundler.Add(&bundledMessage{msg, r}, msg.size)
  222. if err != nil {
  223. r.set("", err)
  224. }
  225. return r
  226. }
  227. // Send all remaining published messages and stop goroutines created for handling
  228. // publishing. Returns once all outstanding messages have been sent or have
  229. // failed to be sent.
  230. func (t *Topic) Stop() {
  231. t.mu.Lock()
  232. noop := t.stopped || t.bundler == nil
  233. t.stopped = true
  234. t.mu.Unlock()
  235. if noop {
  236. return
  237. }
  238. t.bundler.Flush()
  239. }
  240. // A PublishResult holds the result from a call to Publish.
  241. type PublishResult struct {
  242. ready chan struct{}
  243. serverID string
  244. err error
  245. }
  246. // Ready returns a channel that is closed when the result is ready.
  247. // When the Ready channel is closed, Get is guaranteed not to block.
  248. func (r *PublishResult) Ready() <-chan struct{} { return r.ready }
  249. // Get returns the server-generated message ID and/or error result of a Publish call.
  250. // Get blocks until the Publish call completes or the context is done.
  251. func (r *PublishResult) Get(ctx context.Context) (serverID string, err error) {
  252. // If the result is already ready, return it even if the context is done.
  253. select {
  254. case <-r.Ready():
  255. return r.serverID, r.err
  256. default:
  257. }
  258. select {
  259. case <-ctx.Done():
  260. return "", ctx.Err()
  261. case <-r.Ready():
  262. return r.serverID, r.err
  263. }
  264. }
  265. func (r *PublishResult) set(sid string, err error) {
  266. r.serverID = sid
  267. r.err = err
  268. close(r.ready)
  269. }
  270. type bundledMessage struct {
  271. msg *Message
  272. res *PublishResult
  273. }
  274. func (t *Topic) initBundler() {
  275. t.mu.RLock()
  276. noop := t.stopped || t.bundler != nil
  277. t.mu.RUnlock()
  278. if noop {
  279. return
  280. }
  281. t.mu.Lock()
  282. defer t.mu.Unlock()
  283. // Must re-check, since we released the lock.
  284. if t.stopped || t.bundler != nil {
  285. return
  286. }
  287. timeout := t.PublishSettings.Timeout
  288. t.bundler = bundler.NewBundler(&bundledMessage{}, func(items interface{}) {
  289. // TODO(jba): use a context detached from the one passed to NewClient.
  290. ctx := context.TODO()
  291. if timeout != 0 {
  292. var cancel func()
  293. ctx, cancel = context.WithTimeout(ctx, timeout)
  294. defer cancel()
  295. }
  296. t.publishMessageBundle(ctx, items.([]*bundledMessage))
  297. })
  298. t.bundler.DelayThreshold = t.PublishSettings.DelayThreshold
  299. t.bundler.BundleCountThreshold = t.PublishSettings.CountThreshold
  300. if t.bundler.BundleCountThreshold > MaxPublishRequestCount {
  301. t.bundler.BundleCountThreshold = MaxPublishRequestCount
  302. }
  303. t.bundler.BundleByteThreshold = t.PublishSettings.ByteThreshold
  304. t.bundler.BufferedByteLimit = maxInt
  305. t.bundler.BundleByteLimit = MaxPublishRequestBytes
  306. // Unless overridden, allow many goroutines per CPU to call the Publish RPC concurrently.
  307. // The default value was determined via extensive load testing (see the loadtest subdirectory).
  308. if t.PublishSettings.NumGoroutines > 0 {
  309. t.bundler.HandlerLimit = t.PublishSettings.NumGoroutines
  310. } else {
  311. t.bundler.HandlerLimit = 25 * runtime.GOMAXPROCS(0)
  312. }
  313. }
  314. func (t *Topic) publishMessageBundle(ctx context.Context, bms []*bundledMessage) {
  315. pbMsgs := make([]*pb.PubsubMessage, len(bms))
  316. for i, bm := range bms {
  317. pbMsgs[i] = &pb.PubsubMessage{
  318. Data: bm.msg.Data,
  319. Attributes: bm.msg.Attributes,
  320. }
  321. bm.msg = nil // release bm.msg for GC
  322. }
  323. res, err := t.c.pubc.Publish(ctx, &pb.PublishRequest{
  324. Topic: t.name,
  325. Messages: pbMsgs,
  326. }, gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(maxSendRecvBytes)))
  327. for i, bm := range bms {
  328. if err != nil {
  329. bm.res.set("", err)
  330. } else {
  331. bm.res.set(res.MessageIds[i], nil)
  332. }
  333. }
  334. }