@@ -16,26 +16,31 @@ import (
16
16
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
17
17
)
18
18
19
- type batch struct {
20
- ctx context.Context
21
- req request.Request
22
- done multiDone
23
- created time.Time
24
- }
25
-
26
19
// defaultBatcher continuously batch incoming requests and flushes asynchronously if minimum size limit is met or on timeout.
27
20
type defaultBatcher struct {
28
- batchCfg BatchConfig
29
- workerPool chan struct {}
30
- consumeFunc sender.SendFunc [request.Request ]
31
- stopWG sync.WaitGroup
32
- currentBatchMu sync.Mutex
33
- currentBatch * batch
34
- ticker * time.Ticker
35
- shutdownCh chan struct {}
21
+ batchCfg BatchConfig
22
+ workerPool chan struct {}
23
+ consumeFunc sender.SendFunc [request.Request ]
24
+ batchManager batchManager
25
+ stopWG sync.WaitGroup
26
+ ticker * time.Ticker
27
+ shutdownCh chan struct {}
28
+ }
29
+
30
+ func newDefaultBatcher (
31
+ batchCfg BatchConfig ,
32
+ consumeFunc sender.SendFunc [request.Request ],
33
+ maxWorkers int ,
34
+ ) * defaultBatcher {
35
+ return newDefaultBatcherWithKeyFunc (batchCfg , nil , consumeFunc , maxWorkers )
36
36
}
37
37
38
- func newDefaultBatcher (batchCfg BatchConfig , consumeFunc sender.SendFunc [request.Request ], maxWorkers int ) * defaultBatcher {
38
+ func newDefaultBatcherWithKeyFunc (
39
+ batchCfg BatchConfig ,
40
+ keyFunc KeyFunc ,
41
+ consumeFunc sender.SendFunc [request.Request ],
42
+ maxWorkers int ,
43
+ ) * defaultBatcher {
39
44
// TODO: Determine what is the right behavior for this in combination with async queue.
40
45
var workerPool chan struct {}
41
46
if maxWorkers != 0 {
@@ -45,22 +50,24 @@ func newDefaultBatcher(batchCfg BatchConfig, consumeFunc sender.SendFunc[request
45
50
}
46
51
}
47
52
return & defaultBatcher {
48
- batchCfg : batchCfg ,
49
- workerPool : workerPool ,
50
- consumeFunc : consumeFunc ,
51
- stopWG : sync.WaitGroup {},
52
- shutdownCh : make (chan struct {}, 1 ),
53
+ batchCfg : batchCfg ,
54
+ workerPool : workerPool ,
55
+ consumeFunc : consumeFunc ,
56
+ batchManager : newBatchManager (keyFunc ),
57
+ stopWG : sync.WaitGroup {},
58
+ shutdownCh : make (chan struct {}, 1 ),
53
59
}
54
60
}
55
61
56
62
func (qb * defaultBatcher ) Consume (ctx context.Context , req request.Request , done Done ) {
57
- qb .currentBatchMu .Lock ()
63
+ batchEntry := qb .batchManager .getBatch (ctx , req )
64
+ batchEntry .mu .Lock ()
58
65
59
- if qb . currentBatch == nil {
66
+ if batchEntry . batch == nil {
60
67
reqList , mergeSplitErr := req .MergeSplit (ctx , qb .batchCfg .MaxSize , exporterbatcher .SizerTypeItems , nil )
61
68
if mergeSplitErr != nil || len (reqList ) == 0 {
62
69
done .OnDone (mergeSplitErr )
63
- qb . currentBatchMu .Unlock ()
70
+ batchEntry . mu .Unlock ()
64
71
return
65
72
}
66
73
@@ -75,27 +82,27 @@ func (qb *defaultBatcher) Consume(ctx context.Context, req request.Request, done
75
82
if lastReq .ItemsCount () < qb .batchCfg .MinSize {
76
83
// Do not flush the last item and add it to the current batch.
77
84
reqList = reqList [:len (reqList )- 1 ]
78
- qb . currentBatch = & batch {
85
+ batchEntry . batch = & batch {
79
86
ctx : ctx ,
80
87
req : lastReq ,
81
88
done : multiDone {done },
82
89
created : time .Now (),
83
90
}
84
91
}
85
92
86
- qb . currentBatchMu .Unlock ()
93
+ batchEntry . mu .Unlock ()
87
94
for i := 0 ; i < len (reqList ); i ++ {
88
95
qb .flush (ctx , reqList [i ], done )
89
96
}
90
97
91
98
return
92
99
}
93
100
94
- reqList , mergeSplitErr := qb . currentBatch .req .MergeSplit (ctx , qb .batchCfg .MaxSize , exporterbatcher .SizerTypeItems , req )
101
+ reqList , mergeSplitErr := batchEntry .req .MergeSplit (ctx , qb .batchCfg .MaxSize , exporterbatcher .SizerTypeItems , req )
95
102
// If failed to merge signal all Done callbacks from current batch as well as the current request and reset the current batch.
96
103
if mergeSplitErr != nil || len (reqList ) == 0 {
97
104
done .OnDone (mergeSplitErr )
98
- qb . currentBatchMu .Unlock ()
105
+ batchEntry . mu .Unlock ()
99
106
return
100
107
}
101
108
@@ -111,15 +118,15 @@ func (qb *defaultBatcher) Consume(ctx context.Context, req request.Request, done
111
118
112
119
// Logic on how to deal with the current batch:
113
120
// TODO: Deal with merging Context.
114
- qb . currentBatch .req = reqList [0 ]
115
- qb . currentBatch . done = append (qb . currentBatch .done , done )
121
+ batchEntry .req = reqList [0 ]
122
+ batchEntry . done = append (batchEntry .done , done )
116
123
// Save the "currentBatch" if we need to flush it, because we want to execute flush without holding the lock, and
117
124
// cannot unlock and re-lock because we are not done processing all the responses.
118
125
var firstBatch * batch
119
126
// Need to check the currentBatch if more than 1 result returned or if 1 result return but larger than MinSize.
120
- if len (reqList ) > 1 || qb . currentBatch .req .ItemsCount () >= qb .batchCfg .MinSize {
121
- firstBatch = qb . currentBatch
122
- qb . currentBatch = nil
127
+ if len (reqList ) > 1 || batchEntry .req .ItemsCount () >= qb .batchCfg .MinSize {
128
+ firstBatch = batchEntry . batch
129
+ batchEntry . batch = nil
123
130
}
124
131
// At this moment we dealt with the first result which is iter in the currentBatch or in the `firstBatch` we will flush.
125
132
reqList = reqList [1 :]
@@ -130,7 +137,7 @@ func (qb *defaultBatcher) Consume(ctx context.Context, req request.Request, done
130
137
if lastReq .ItemsCount () < qb .batchCfg .MinSize {
131
138
// Do not flush the last item and add it to the current batch.
132
139
reqList = reqList [:len (reqList )- 1 ]
133
- qb . currentBatch = & batch {
140
+ batchEntry . batch = & batch {
134
141
ctx : ctx ,
135
142
req : lastReq ,
136
143
done : multiDone {done },
@@ -139,7 +146,7 @@ func (qb *defaultBatcher) Consume(ctx context.Context, req request.Request, done
139
146
}
140
147
}
141
148
142
- qb . currentBatchMu .Unlock ()
149
+ batchEntry . mu .Unlock ()
143
150
if firstBatch != nil {
144
151
qb .flush (firstBatch .ctx , firstBatch .req , firstBatch .done )
145
152
}
@@ -176,21 +183,23 @@ func (qb *defaultBatcher) Start(_ context.Context, _ component.Host) error {
176
183
177
184
// flushCurrentBatchIfNecessary sends out the current request batch if it is not nil
178
185
func (qb * defaultBatcher ) flushCurrentBatchIfNecessary (forceFlush bool ) {
179
- qb .currentBatchMu .Lock ()
180
- if qb .currentBatch == nil {
181
- qb .currentBatchMu .Unlock ()
182
- return
183
- }
184
- if ! forceFlush && time .Since (qb .currentBatch .created ) < qb .batchCfg .FlushTimeout {
185
- qb .currentBatchMu .Unlock ()
186
- return
187
- }
188
- batchToFlush := qb .currentBatch
189
- qb .currentBatch = nil
190
- qb .currentBatchMu .Unlock ()
186
+ qb .batchManager .forEachBatch (func (batchEntry * batchEntry ) {
187
+ batchEntry .mu .Lock ()
188
+ if batchEntry .batch == nil {
189
+ batchEntry .mu .Unlock ()
190
+ return
191
+ }
192
+ if ! forceFlush && time .Since (batchEntry .created ) < qb .batchCfg .FlushTimeout {
193
+ batchEntry .mu .Unlock ()
194
+ return
195
+ }
196
+ batchToFlush := batchEntry .batch
197
+ batchEntry .batch = nil
198
+ batchEntry .mu .Unlock ()
191
199
192
- // flush() blocks until successfully started a goroutine for flushing.
193
- qb .flush (batchToFlush .ctx , batchToFlush .req , batchToFlush .done )
200
+ // flush() blocks until successfully started a goroutine for flushing.
201
+ qb .flush (batchToFlush .ctx , batchToFlush .req , batchToFlush .done )
202
+ })
194
203
}
195
204
196
205
// flush starts a goroutine that calls consumeFunc. It blocks until a worker is available if necessary.
0 commit comments