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 //------------------------------------------------------------------------------