github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/sync_batcher.go (about) 1 package reader 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/Jeffail/benthos/v3/internal/interop" 9 "github.com/Jeffail/benthos/v3/lib/log" 10 "github.com/Jeffail/benthos/v3/lib/message/batch" 11 "github.com/Jeffail/benthos/v3/lib/metrics" 12 "github.com/Jeffail/benthos/v3/lib/types" 13 ) 14 15 //------------------------------------------------------------------------------ 16 17 // SyncBatcher is a wrapper for reader.Sync implementations that applies a 18 // batching policy to incoming payloads. Once a batch is created and sent the 19 // next Acknowledge call applies to all messages of the prior batches. 20 type SyncBatcher struct { 21 batcher *batch.Policy 22 r Sync 23 ctx context.Context 24 close func() 25 } 26 27 // NewSyncBatcher returns a new SyncBatcher wrapper around a reader.Async. 28 func NewSyncBatcher( 29 batchConfig batch.PolicyConfig, 30 r Sync, 31 mgr types.Manager, 32 log log.Modular, 33 stats metrics.Type, 34 ) (*SyncBatcher, error) { 35 mgr, log, stats = interop.LabelChild("batching", mgr, log, stats) 36 policy, err := batch.NewPolicy(batchConfig, mgr, log, stats) 37 if err != nil { 38 return nil, fmt.Errorf("failed to construct batch policy: %v", err) 39 } 40 ctx, cancel := context.WithCancel(context.Background()) 41 return &SyncBatcher{ 42 batcher: policy, 43 r: r, 44 ctx: ctx, 45 close: cancel, 46 }, nil 47 } 48 49 //------------------------------------------------------------------------------ 50 51 // Connect attempts to establish a connection to the source, if unsuccessful 52 // returns an error. If the attempt is successful (or not necessary) returns 53 // nil. 54 func (p *SyncBatcher) Connect() error { 55 return p.ConnectWithContext(p.ctx) 56 } 57 58 // ConnectWithContext attempts to establish a connection to the source, if 59 // unsuccessful returns an error. If the attempt is successful (or not 60 // necessary) returns nil. 61 func (p *SyncBatcher) ConnectWithContext(ctx context.Context) error { 62 return p.r.ConnectWithContext(ctx) 63 } 64 65 // Read attempts to read a new message from the source. 66 func (p *SyncBatcher) Read() (types.Message, error) { 67 return p.ReadNextWithContext(p.ctx) 68 } 69 70 // ReadNextWithContext attempts to read a new message from the source. 71 func (p *SyncBatcher) ReadNextWithContext(ctx context.Context) (types.Message, error) { 72 var forcedBatchDeadline time.Time 73 if tout := p.batcher.UntilNext(); tout >= 0 { 74 forcedBatchDeadline = time.Now().Add(tout) 75 var cancel context.CancelFunc 76 ctx, cancel = context.WithDeadline(ctx, forcedBatchDeadline) 77 defer cancel() 78 } 79 80 flushBatch := false 81 for !flushBatch { 82 msg, err := p.r.ReadNextWithContext(ctx) 83 if err != nil { 84 if !forcedBatchDeadline.IsZero() && !time.Now().Before(forcedBatchDeadline) { 85 if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 { 86 return batch, nil 87 } 88 } 89 if err == types.ErrTimeout { 90 // If the call "timed out" it could either mean that the context 91 // was cancelled, in which case we want to return right now, or 92 // that the underlying mechanism timed out, in which case we 93 // simply want to try again. 94 select { 95 case <-ctx.Done(): 96 if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 { 97 return batch, nil 98 } 99 return nil, types.ErrTimeout 100 default: 101 } 102 continue 103 } 104 if err == types.ErrTypeClosed { 105 if batch := p.batcher.Flush(); batch != nil && batch.Len() > 0 { 106 return batch, nil 107 } 108 } 109 return nil, err 110 } 111 112 msg.Iter(func(i int, part types.Part) error { 113 flushBatch = p.batcher.Add(part) || flushBatch 114 return nil 115 }) 116 } 117 118 msg := p.batcher.Flush() 119 if msg == nil || msg.Len() == 0 { 120 return nil, types.ErrTimeout 121 } 122 return msg, nil 123 } 124 125 // Acknowledge confirms whether or not our unacknowledged messages have been 126 // successfully propagated or not. 127 func (p *SyncBatcher) Acknowledge(err error) error { 128 return p.AcknowledgeWithContext(context.Background(), err) 129 } 130 131 // AcknowledgeWithContext confirms whether or not our unacknowledged messages 132 // have been successfully propagated or not. 133 func (p *SyncBatcher) AcknowledgeWithContext(ctx context.Context, err error) error { 134 return p.r.AcknowledgeWithContext(ctx, err) 135 } 136 137 // CloseAsync triggers the asynchronous closing of the reader. 138 func (p *SyncBatcher) CloseAsync() { 139 p.close() 140 p.r.CloseAsync() 141 } 142 143 // WaitForClose blocks until either the reader is finished closing or a timeout 144 // occurs. 145 func (p *SyncBatcher) WaitForClose(tout time.Duration) error { 146 tstop := time.Now().Add(tout) 147 err := p.r.WaitForClose(time.Until(tstop)) 148 p.batcher.CloseAsync() 149 if err != nil { 150 return err 151 } 152 return p.batcher.WaitForClose(time.Until(tstop)) 153 } 154 155 //------------------------------------------------------------------------------