github.com/Jeffail/benthos/v3@v3.65.0/public/service/config_batch_policy.go (about) 1 package service 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/Jeffail/benthos/v3/internal/docs" 9 "github.com/Jeffail/benthos/v3/lib/message/batch" 10 "github.com/Jeffail/benthos/v3/lib/processor" 11 "github.com/Jeffail/benthos/v3/lib/types" 12 "gopkg.in/yaml.v3" 13 ) 14 15 // BatchPolicy describes the mechanisms by which batching should be performed of 16 // messages destined for a Batch output. This is returned by constructors of 17 // batch outputs. 18 type BatchPolicy struct { 19 ByteSize int 20 Count int 21 Check string 22 Period string 23 24 // Only available when using NewBatchPolicyField. 25 procs []processor.Config 26 } 27 28 func (b BatchPolicy) toInternal() batch.PolicyConfig { 29 batchConf := batch.NewPolicyConfig() 30 batchConf.ByteSize = b.ByteSize 31 batchConf.Count = b.Count 32 batchConf.Check = b.Check 33 batchConf.Period = b.Period 34 batchConf.Processors = b.procs 35 return batchConf 36 } 37 38 // Batcher provides a batching mechanism where messages can be added one-by-one 39 // with a boolean return indicating whether the batching policy has been 40 // triggered. 41 // 42 // Upon triggering the policy it is the responsibility of the owner of this 43 // batcher to call Flush, which returns all the pending messages in the batch. 44 // 45 // This batcher may contain processors that are executed during the flush, 46 // therefore it is important to call Close when this batcher is no longer 47 // required, having also called Flush if appropriate. 48 type Batcher struct { 49 p *batch.Policy 50 } 51 52 // Add a message to the batch. Returns true if the batching policy has been 53 // triggered by this new addition, in which case Flush should be called. 54 func (b *Batcher) Add(msg *Message) bool { 55 return b.p.Add(msg.part) 56 } 57 58 // UntilNext returns a duration indicating how long until the current batch 59 // should be flushed due to a configured period. A boolean is also returned 60 // indicating whether the batching policy has a timed factor, if this is false 61 // then the duration returned should be ignored. 62 func (b *Batcher) UntilNext() (time.Duration, bool) { 63 t := b.p.UntilNext() 64 if t >= 0 { 65 return t, true 66 } 67 return 0, false 68 } 69 70 // Flush pending messages into a batch, apply any batching processors that are 71 // part of the batching policy, and then return the result. 72 func (b *Batcher) Flush(ctx context.Context) (batch MessageBatch, err error) { 73 m := b.p.Flush() 74 if m == nil || m.Len() == 0 { 75 return 76 } 77 _ = m.Iter(func(i int, part types.Part) error { 78 batch = append(batch, newMessageFromPart(part)) 79 return nil 80 }) 81 return 82 } 83 84 // Close the batching policy, which cleans up any resources used by batching 85 // processors. 86 func (b *Batcher) Close(ctx context.Context) error { 87 b.p.CloseAsync() 88 for { 89 // Gross but will do for now until we replace these with context params. 90 if err := b.p.WaitForClose(time.Millisecond * 100); err == nil { 91 return nil 92 } 93 select { 94 case <-ctx.Done(): 95 return ctx.Err() 96 default: 97 } 98 } 99 } 100 101 // NewBatcher creates a batching mechanism from the policy. 102 func (b BatchPolicy) NewBatcher(res *Resources) (*Batcher, error) { 103 p, err := batch.NewPolicy(b.toInternal(), res.mgr, res.mgr.Logger(), res.mgr.Metrics()) 104 if err != nil { 105 return nil, err 106 } 107 return &Batcher{p}, nil 108 } 109 110 //------------------------------------------------------------------------------ 111 112 // NewBatchPolicyField defines a new object type config field that describes a 113 // batching policy for batched outputs. It is then possible to extract a 114 // BatchPolicy from the resulting parsed config with the method 115 // FieldBatchPolicy. 116 func NewBatchPolicyField(name string) *ConfigField { 117 bs := batch.FieldSpec() 118 bs.Name = name 119 bs.Type = docs.FieldTypeObject 120 var newChildren []docs.FieldSpec 121 for _, f := range bs.Children { 122 if f.Name == "count" { 123 f = f.HasDefault(0) 124 } 125 if !f.IsDeprecated { 126 newChildren = append(newChildren, f) 127 } 128 } 129 bs.Children = newChildren 130 return &ConfigField{field: bs} 131 } 132 133 // FieldBatchPolicy accesses a field from a parsed config that was defined with 134 // NewBatchPolicyField and returns a BatchPolicy, or an error if the 135 // configuration was invalid. 136 func (p *ParsedConfig) FieldBatchPolicy(path ...string) (conf BatchPolicy, err error) { 137 if conf.Count, err = p.FieldInt(append(path, "count")...); err != nil { 138 return conf, err 139 } 140 if conf.ByteSize, err = p.FieldInt(append(path, "byte_size")...); err != nil { 141 return conf, err 142 } 143 if conf.Check, err = p.FieldString(append(path, "check")...); err != nil { 144 return conf, err 145 } 146 if conf.Period, err = p.FieldString(append(path, "period")...); err != nil { 147 return conf, err 148 } 149 150 procsNode, exists := p.field(append(path, "processors")...) 151 if !exists { 152 return 153 } 154 155 procsArray, ok := procsNode.([]interface{}) 156 if !ok { 157 err = fmt.Errorf("field 'processors' returned unexpected value, expected array, got %T", procsNode) 158 return 159 } 160 161 for i, iConf := range procsArray { 162 node, ok := iConf.(*yaml.Node) 163 if !ok { 164 err = fmt.Errorf("field 'processors.%v' returned unexpected value, expected object, got %T", i, iConf) 165 return 166 } 167 168 var pconf processor.Config 169 if err = node.Decode(&pconf); err != nil { 170 err = fmt.Errorf("field 'processors.%v': %w", i, err) 171 return 172 } 173 conf.procs = append(conf.procs, pconf) 174 } 175 return 176 }