forked from open-telemetry/opentelemetry-collector
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathid_batcher.go
More file actions
142 lines (123 loc) · 5.06 KB
/
Copy pathid_batcher.go
File metadata and controls
142 lines (123 loc) · 5.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package idbatcher defines a pipeline of fixed size in which the
// elements are batches of ids.
package idbatcher
import (
"errors"
"sync"
)
var (
// ErrInvalidNumBatches occurs when an invalid number of batches is specified.
ErrInvalidNumBatches = errors.New("invalid number of batches, it must be greater than zero")
// ErrInvalidBatchChannelSize occurs when an invalid batch channel size is specified.
ErrInvalidBatchChannelSize = errors.New("invalid batch channel size, it must be greater than zero")
)
// ID is the type of each element in the batch.
type ID []byte
// Batch is the type of batches held by the Batcher.
type Batch []ID
// Batcher behaves like a pipeline of batches that has a fixed number of batches in the pipe
// and a new batch being built outside of the pipe. Items can be concurrently added to the batch
// currently being built. When the batch being built is closed, the oldest batch in the pipe
// is pushed out so the one just closed can be put on the end of the pipe (this is done as an
// atomic operation). The caller is in control of when a batch is completed and a new one should
// be started.
type Batcher interface {
// AddToCurrentBatch puts the given id on the batch being currently built. The client is in charge
// of limiting the growth of the current batch if appropriate for its scenario. It can
// either call CloseCurrentAndTakeFirstBatch earlier or stop adding new items depending on what is
// required by the scenario.
AddToCurrentBatch(id ID)
// CloseCurrentAndTakeFirstBatch takes the batch at the front of the pipe, and moves the current
// batch to the end of the pipe, creating a new batch to receive new items. This operation should
// be atomic.
// It returns the batch that was in front of the pipe and a boolean that if true indicates that
// there are more batches to be retrieved.
CloseCurrentAndTakeFirstBatch() (Batch, bool)
// Stop informs that no more items are going to be batched and the pipeline can be read until it
// is empty. After this method is called attempts to enqueue new items will panic.
Stop()
}
var _ Batcher = (*batcher)(nil)
type batcher struct {
pendingIds chan ID // Channel for the ids to be added to the next batch.
batches chan Batch // Channel with already captured batches.
// cbMutex protects the currentBatch storing ids.
cbMutex sync.Mutex
currentBatch Batch
newBatchesInitialCapacity uint64
stopchan chan bool
stopped bool
}
// New creates a Batcher that will hold numBatches in its pipeline, having a channel with
// batchChannelSize to receive new items. New batches will be created with capacity set to
// newBatchesInitialCapacity.
func New(numBatches, newBatchesInitialCapacity, batchChannelSize uint64) (Batcher, error) {
if numBatches < 1 {
return nil, ErrInvalidNumBatches
}
if batchChannelSize < 1 {
return nil, ErrInvalidBatchChannelSize
}
batches := make(chan Batch, numBatches)
// First numBatches batches will be empty in order to simplify clients that are running
// CloseCurrentAndTakeFirstBatch on a timer and want to delay the processing of the first
// batch with actual data. This way there is no need for accounting on the client side and
// a single timer can be started immediately.
for i := uint64(0); i < numBatches; i++ {
batches <- nil
}
batcher := &batcher{
pendingIds: make(chan ID, batchChannelSize),
batches: batches,
currentBatch: make(Batch, 0, newBatchesInitialCapacity),
newBatchesInitialCapacity: newBatchesInitialCapacity,
stopchan: make(chan bool),
}
// Single goroutine that keeps filling the current batch, contention is expected only
// when the current batch is being switched.
go func() {
for id := range batcher.pendingIds {
batcher.cbMutex.Lock()
batcher.currentBatch = append(batcher.currentBatch, id)
batcher.cbMutex.Unlock()
}
batcher.stopchan <- true
}()
return batcher, nil
}
func (b *batcher) AddToCurrentBatch(id ID) {
b.pendingIds <- id
}
func (b *batcher) CloseCurrentAndTakeFirstBatch() (Batch, bool) {
if readBatch, ok := <-b.batches; ok {
if !b.stopped {
nextBatch := make(Batch, 0, b.newBatchesInitialCapacity)
b.cbMutex.Lock()
b.batches <- b.currentBatch
b.currentBatch = nextBatch
b.cbMutex.Unlock()
}
return readBatch, true
}
readBatch := b.currentBatch
b.currentBatch = nil
return readBatch, false
}
func (b *batcher) Stop() {
close(b.pendingIds)
b.stopped = <-b.stopchan
close(b.batches)
}