@@ -26,6 +26,13 @@ type EventLoop struct {
26
26
preparer FirstEventBatchPreparer
27
27
eventCh <- chan interface {}
28
28
logger logr.Logger
29
+
30
+ // The EventLoop uses double buffering to handle event batch processing.
31
+ // The goroutine that handles the batch will always read from the currentBatch slice.
32
+ // While the current batch is being handled, new events are added to the nextBatch slice.
33
+ // The batches are swapped before starting the handler goroutine.
34
+ currentBatch EventBatch
35
+ nextBatch EventBatch
29
36
}
30
37
31
38
// NewEventLoop creates a new EventLoop.
@@ -36,37 +43,38 @@ func NewEventLoop(
36
43
preparer FirstEventBatchPreparer ,
37
44
) * EventLoop {
38
45
return & EventLoop {
39
- eventCh : eventCh ,
40
- logger : logger ,
41
- handler : handler ,
42
- preparer : preparer ,
46
+ eventCh : eventCh ,
47
+ logger : logger ,
48
+ handler : handler ,
49
+ preparer : preparer ,
50
+ currentBatch : make (EventBatch , 0 ),
51
+ nextBatch : make (EventBatch , 0 ),
43
52
}
44
53
}
45
54
46
55
// Start starts the EventLoop.
47
56
// This method will block until the EventLoop stops, which will happen after the ctx is closed.
48
57
func (el * EventLoop ) Start (ctx context.Context ) error {
49
- // The current batch.
50
- var batch EventBatch
51
58
// handling tells if any batch is currently being handled.
52
59
var handling bool
53
60
// handlingDone is used to signal the completion of handling a batch.
54
61
handlingDone := make (chan struct {})
55
62
56
- handleAndResetBatch := func () {
63
+ handleBatch := func () {
57
64
go func (batch EventBatch ) {
58
65
el .logger .Info ("Handling events from the batch" , "total" , len (batch ))
59
66
60
67
el .handler .HandleEventBatch (ctx , batch )
61
68
62
69
el .logger .Info ("Finished handling the batch" )
63
70
handlingDone <- struct {}{}
64
- }(batch )
71
+ }(el .currentBatch )
72
+ }
65
73
66
- // FIXME(pleshakov): Making an entirely new buffer is inefficient and multiplies memory operations.
67
- // Use a double-buffer approach - create two buffers and exchange them between the producer and consumer
68
- // routines. NOTE: pass-by-reference, and reset buffer to length 0, but retain capacity.
69
- batch = make ([] interface {}, 0 )
74
+ swapAndHandleBatch := func () {
75
+ el . swapBatches ()
76
+ handleBatch ()
77
+ handling = true
70
78
}
71
79
72
80
// Prepare the fist event batch, which includes the UpsertEvents for all relevant cluster resources.
@@ -81,13 +89,13 @@ func (el *EventLoop) Start(ctx context.Context) error {
81
89
// not trigger any reconfiguration after receiving an upsert for an existing resource with the same Generation.
82
90
83
91
var err error
84
- batch , err = el .preparer .Prepare (ctx )
92
+ el . currentBatch , err = el .preparer .Prepare (ctx )
85
93
if err != nil {
86
94
return fmt .Errorf ("failed to prepare the first batch: %w" , err )
87
95
}
88
96
89
97
// Handle the first batch
90
- handleAndResetBatch ()
98
+ handleBatch ()
91
99
handling = true
92
100
93
101
// Note: at any point of time, no more than one batch is currently being handled.
@@ -103,28 +111,32 @@ func (el *EventLoop) Start(ctx context.Context) error {
103
111
return nil
104
112
case e := <- el .eventCh :
105
113
// Add the event to the current batch.
106
- batch = append (batch , e )
114
+ el . nextBatch = append (el . nextBatch , e )
107
115
108
116
// FIXME(pleshakov): Log more details about the event like resource GVK and ns/name.
109
117
el .logger .Info (
110
- "added an event to the current batch" ,
118
+ "added an event to the next batch" ,
111
119
"type" , fmt .Sprintf ("%T" , e ),
112
- "total" , len (batch ),
120
+ "total" , len (el . nextBatch ),
113
121
)
114
122
115
- // Handle the current batch if no batch is being handled.
123
+ // If no batch is currently being handled, swap batches and begin handling the batch .
116
124
if ! handling {
117
- handleAndResetBatch ()
118
- handling = true
125
+ swapAndHandleBatch ()
119
126
}
120
127
case <- handlingDone :
121
128
handling = false
122
129
123
- // Handle the current batch if it has at least one event.
124
- if len (batch ) > 0 {
125
- handleAndResetBatch ()
126
- handling = true
130
+ // If there's at least one event in the next batch, swap batches and begin handling the batch.
131
+ if len (el .nextBatch ) > 0 {
132
+ swapAndHandleBatch ()
127
133
}
128
134
}
129
135
}
130
136
}
137
+
138
+ // swapBatches swaps the current and next batches.
139
+ func (el * EventLoop ) swapBatches () {
140
+ el .currentBatch , el .nextBatch = el .nextBatch , el .currentBatch
141
+ el .nextBatch = el .nextBatch [:0 ]
142
+ }
0 commit comments