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  }