Skip to content

Commit 45f533a

Browse files
committed
eventstream: group replay goroutines in errgroup
By switching from a WaitGroup to an errgroup, a quicker exit was possible with the groups' custom context.
1 parent 2b10c44 commit 45f533a

File tree

2 files changed

+49
-51
lines changed

2 files changed

+49
-51
lines changed

internal/eventstream/client.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import (
1313
"github.com/icinga/icingadb/pkg/icingadb"
1414
"github.com/icinga/icingadb/pkg/logging"
1515
"go.uber.org/zap"
16+
"golang.org/x/sync/errgroup"
1617
"net/http"
1718
"net/url"
1819
"os"
19-
"sync"
2020
"sync/atomic"
2121
"time"
2222
)
@@ -323,25 +323,29 @@ func (client *Client) enterReplayPhase() {
323323
return
324324
}
325325

326-
queryFns := []func(string){client.checkMissedAcknowledgements, client.checkMissedStateChanges}
326+
queryFns := []func(string, context.Context) error{client.checkMissedAcknowledgements, client.checkMissedStateChanges}
327327
objTypes := []string{"host", "service"}
328328

329-
var replayWg sync.WaitGroup
330-
replayWg.Add(len(queryFns) * len(objTypes))
331-
329+
group, groupCtx := errgroup.WithContext(client.Ctx)
332330
for _, fn := range queryFns {
333331
for _, objType := range objTypes {
334-
go func(fn func(string), objType string) {
335-
fn(objType)
336-
replayWg.Done()
337-
}(fn, objType)
332+
fn, objType := fn, objType // https://go.dev/doc/faq#closures_and_goroutines
333+
group.Go(func() error {
334+
return fn(objType, groupCtx)
335+
})
338336
}
339337
}
340338

341339
go func() {
342340
startTime := time.Now()
343-
replayWg.Wait()
344-
client.Logger.Debugw("All replay phase workers have finished", zap.Duration("duration", time.Since(startTime)))
341+
342+
err := group.Wait()
343+
if err != nil {
344+
client.Logger.Errorw("Replaying the API resulted in errors", zap.Error(err), zap.Duration("duration", time.Since(startTime)))
345+
} else {
346+
client.Logger.Debugw("All replay phase workers have finished", zap.Duration("duration", time.Since(startTime)))
347+
}
348+
345349
client.replayTrigger <- struct{}{}
346350
}()
347351
}

internal/eventstream/client_api.go

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -178,42 +178,37 @@ func (client *Client) fetchAcknowledgementComment(host, service string, ackTime
178178
// If a filterExpr is given (non-empty string), it will be used for the query. Otherwise, all objects will be requested.
179179
//
180180
// The callback function will be called f.e. object of the objType (i.e. "host" or "service") being retrieved from the
181-
// Icinga 2 Objects API. The callback function or a later caller must decide if this object should be replayed.
182-
func (client *Client) checkMissedChanges(objType, filterExpr string, attrsCallbackFn func(attrs HostServiceRuntimeAttributes, host, service string)) {
183-
var (
184-
logger = client.Logger.With(zap.String("object type", objType))
185-
186-
jsonRaw io.ReadCloser
187-
err error
188-
)
181+
// Icinga 2 Objects API sequentially. The callback function or a later caller decides if this object should be replayed.
182+
func (client *Client) checkMissedChanges(
183+
objType, filterExpr string,
184+
attrsCallbackFn func(attrs HostServiceRuntimeAttributes, host, service string) error,
185+
) (err error) {
186+
logger := client.Logger.With(zap.String("object type", objType), zap.String("filter expr", filterExpr))
187+
188+
defer func() {
189+
if err != nil {
190+
logger.Errorw("Querying API for replay failed", zap.Error(err))
191+
}
192+
}()
193+
194+
var jsonRaw io.ReadCloser
189195
if filterExpr == "" {
190196
jsonRaw, err = client.queryObjectsApiDirect(objType, "")
191197
} else {
192198
jsonRaw, err = client.queryObjectsApiQuery(objType, map[string]any{"filter": filterExpr})
193199
}
194200
if err != nil {
195-
logger.Errorw("Querying API failed", zap.Error(err))
196201
return
197202
}
198203

199204
objQueriesResults, err := extractObjectQueriesResult[HostServiceRuntimeAttributes](jsonRaw)
200205
if err != nil {
201-
logger.Errorw("Parsing API response failed", zap.Error(err))
202-
return
203-
}
204-
205-
if len(objQueriesResults) == 0 {
206206
return
207207
}
208208

209209
logger.Debugw("Querying API resulted in state changes", zap.Int("changes", len(objQueriesResults)))
210210

211211
for _, objQueriesResult := range objQueriesResults {
212-
if client.Ctx.Err() != nil {
213-
logger.Warnw("Stopping API response processing as context is finished", zap.Error(client.Ctx.Err()))
214-
return
215-
}
216-
217212
var hostName, serviceName string
218213
switch objQueriesResult.Type {
219214
case "Host":
@@ -224,57 +219,56 @@ func (client *Client) checkMissedChanges(objType, filterExpr string, attrsCallba
224219
serviceName = objQueriesResult.Attrs.Name
225220

226221
default:
227-
logger.Errorw("Querying API delivered a wrong object type", zap.String("result type", objQueriesResult.Type))
228-
continue
222+
err = fmt.Errorf("querying API delivered a wrong object type %q", objQueriesResult.Type)
223+
return
229224
}
230225

231-
attrsCallbackFn(objQueriesResult.Attrs, hostName, serviceName)
226+
err = attrsCallbackFn(objQueriesResult.Attrs, hostName, serviceName)
227+
if err != nil {
228+
return
229+
}
232230
}
231+
return
233232
}
234233

235234
// checkMissedStateChanges fetches all objects of the requested type and feeds them into the handler.
236-
func (client *Client) checkMissedStateChanges(objType string) {
237-
client.checkMissedChanges(objType, "", func(attrs HostServiceRuntimeAttributes, host, service string) {
238-
logger := client.Logger.With(zap.String("object type", objType))
239-
235+
func (client *Client) checkMissedStateChanges(objType string, ctx context.Context) error {
236+
return client.checkMissedChanges(objType, "", func(attrs HostServiceRuntimeAttributes, host, service string) error {
240237
ev, err := client.buildHostServiceEvent(attrs.LastCheckResult, attrs.State, host, service)
241238
if err != nil {
242-
logger.Errorw("Failed to construct Event from API", zap.Error(err))
243-
return
239+
return fmt.Errorf("failed to construct Event from API, %w", err)
244240
}
245241

246242
select {
247-
case <-client.Ctx.Done():
248-
logger.Warnw("Cannot dispatch replayed event as context is finished", zap.Error(client.Ctx.Err()))
243+
case <-ctx.Done():
244+
return ctx.Err()
249245
case client.eventDispatcherReplay <- &eventMsg{ev, attrs.LastStateChange.Time}:
246+
return nil
250247
}
251248
})
252249
}
253250

254251
// checkMissedAcknowledgements fetches all Host or Service Acknowledgements and feeds them into the handler.
255252
//
256253
// Currently only active acknowledgements are being processed.
257-
func (client *Client) checkMissedAcknowledgements(objType string) {
254+
func (client *Client) checkMissedAcknowledgements(objType string, ctx context.Context) error {
258255
filterExpr := fmt.Sprintf("%s.acknowledgement", objType)
259-
client.checkMissedChanges(objType, filterExpr, func(attrs HostServiceRuntimeAttributes, host, service string) {
260-
logger := client.Logger.With(zap.String("object type", objType))
261-
256+
return client.checkMissedChanges(objType, filterExpr, func(attrs HostServiceRuntimeAttributes, host, service string) error {
262257
ackComment, err := client.fetchAcknowledgementComment(host, service, attrs.AcknowledgementLastChange.Time)
263258
if err != nil {
264-
logger.Errorw("Cannot fetch ACK Comment for Acknowledgement", zap.Error(err))
265-
return
259+
return fmt.Errorf("cannot fetch ACK Comment for Acknowledgement, %w", err)
266260
}
267261

268262
ev, err := client.buildAcknowledgementEvent(host, service, ackComment.Author, ackComment.Text)
269263
if err != nil {
270-
logger.Errorw("Failed to construct Event from Acknowledgement API", zap.Error(err))
271-
return
264+
return fmt.Errorf("failed to construct Event from Acknowledgement API, %w", err)
272265
}
273266

274267
select {
275-
case <-client.Ctx.Done():
276-
logger.Warnw("Cannot dispatch replayed event as context is finished", zap.Error(client.Ctx.Err()))
268+
case <-ctx.Done():
269+
return ctx.Err()
277270
case client.eventDispatcherReplay <- &eventMsg{ev, attrs.AcknowledgementLastChange.Time}:
271+
return nil
278272
}
279273
})
280274
}

0 commit comments

Comments
 (0)