Skip to content

Commit 5e95658

Browse files
committed
fix: race conditions in check shutdown & oapi version
* fix: race conditions in check shutdown * fix: set openapi version on build time * refactor: check base naming Signed-off-by: lvlcn-t <[email protected]>
1 parent e4d47a1 commit 5e95658

File tree

20 files changed

+149
-143
lines changed

20 files changed

+149
-143
lines changed

.github/workflows/test_unit.yml renamed to .github/workflows/test_go.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Test - Unit
1+
name: Test Go
22

33
on:
44
push:
@@ -19,7 +19,7 @@ jobs:
1919
with:
2020
go-version-file: go.mod
2121

22-
- name: Test
22+
- name: Run all go tests
2323
run: |
2424
go mod download
25-
go test --race --count=1 --coverprofile cover.out -v ./...
25+
go test -race -count=1 -coverprofile cover.out -v ./...

cmd/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func NewCmdRun() *cobra.Command {
6666

6767
// run is the entry point to start the sparrow
6868
func run() func(cmd *cobra.Command, args []string) error {
69-
return func(cmd *cobra.Command, _ []string) error {
70-
cfg := &config.Config{Version: cmd.Root().Version}
69+
return func(_ *cobra.Command, _ []string) error {
70+
cfg := &config.Config{}
7171
err := viper.Unmarshal(cfg)
7272
if err != nil {
7373
return fmt.Errorf("failed to parse config: %w", err)

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ package main
2020

2121
import (
2222
"github.com/caas-team/sparrow/cmd"
23+
"github.com/caas-team/sparrow/pkg"
2324
)
2425

2526
// Version is the current version of sparrow
2627
// It is set at build time by using -ldflags "-X main.version=x.x.x"
2728
var version string
2829

30+
func init() { //nolint:gochecknoinits // Required for version to be set on build
31+
pkg.Version = version
32+
}
33+
2934
func main() {
3035
cmd.Execute(version)
3136
}

pkg/checks/base.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,25 @@ type Check interface {
6161
RemoveLabelledMetrics(target string) error
6262
}
6363

64-
// CheckBase is a struct providing common fields used by implementations of the Check interface.
64+
// Base is a struct providing common fields and methods used by implementations of the [Check] interface.
6565
// It serves as a foundational structure that should be embedded in specific check implementations.
66-
type CheckBase struct {
66+
type Base struct {
6767
// Mutex for thread-safe access to shared resources within the check implementation
6868
Mu sync.Mutex
6969
// Signal channel used to notify about shutdown of a check
7070
DoneChan chan struct{}
71+
// closed is a flag indicating if the check has been shut down.
72+
closed bool
73+
}
74+
75+
// Shutdown closes the DoneChan to signal the check to stop running.
76+
func (b *Base) Shutdown() {
77+
b.Mu.Lock()
78+
defer b.Mu.Unlock()
79+
if !b.closed {
80+
close(b.DoneChan)
81+
b.closed = true
82+
}
7183
}
7284

7385
// Runtime is the interface that all check configurations must implement

pkg/checks/base_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package checks
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestBase_Shutdown(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
b *Base
13+
}{
14+
{
15+
name: "shutdown",
16+
b: &Base{
17+
DoneChan: make(chan struct{}, 1),
18+
},
19+
},
20+
{
21+
name: "already shutdown",
22+
b: &Base{
23+
DoneChan: make(chan struct{}, 1),
24+
closed: true,
25+
},
26+
},
27+
}
28+
for _, tt := range tests {
29+
t.Run(tt.name, func(t *testing.T) {
30+
if tt.b.closed {
31+
close(tt.b.DoneChan)
32+
}
33+
tt.b.Shutdown()
34+
35+
if !tt.b.closed {
36+
t.Error("Base.Shutdown() should close DoneChan")
37+
}
38+
39+
assert.Panics(t, func() {
40+
tt.b.DoneChan <- struct{}{}
41+
}, "Base.DoneChan should be closed")
42+
})
43+
}
44+
}

pkg/checks/dns/dns.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const CheckName = "dns"
4141

4242
// DNS is a check that resolves the names and addresses
4343
type DNS struct {
44-
checks.CheckBase
44+
checks.Base
4545
config Config
4646
metrics metrics
4747
client Resolver
@@ -60,7 +60,7 @@ func (d *DNS) Name() string {
6060
// NewCheck creates a new instance of the dns check
6161
func NewCheck() checks.Check {
6262
return &DNS{
63-
CheckBase: checks.CheckBase{
63+
Base: checks.Base{
6464
Mu: sync.Mutex{},
6565
DoneChan: make(chan struct{}, 1),
6666
},
@@ -108,11 +108,6 @@ func (d *DNS) Run(ctx context.Context, cResult chan checks.ResultDTO) error {
108108
}
109109
}
110110

111-
func (d *DNS) Shutdown() {
112-
d.DoneChan <- struct{}{}
113-
close(d.DoneChan)
114-
}
115-
116111
func (d *DNS) UpdateConfig(cfg checks.Runtime) error {
117112
if c, ok := cfg.(*Config); ok {
118113
d.Mu.Lock()

pkg/checks/dns/dns_test.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestDNS_Run(t *testing.T) {
5151
name: "success with no targets",
5252
mockSetup: func() *DNS {
5353
return &DNS{
54-
CheckBase: checks.CheckBase{
54+
Base: checks.Base{
5555
Mu: sync.Mutex{},
5656
DoneChan: make(chan struct{}, 1),
5757
},
@@ -241,21 +241,6 @@ func TestDNS_Run_Context_Done(t *testing.T) {
241241
time.Sleep(time.Millisecond * 30)
242242
}
243243

244-
func TestDNS_Shutdown(t *testing.T) {
245-
cDone := make(chan struct{}, 1)
246-
c := DNS{
247-
CheckBase: checks.CheckBase{
248-
DoneChan: cDone,
249-
},
250-
}
251-
c.Shutdown()
252-
253-
_, ok := <-cDone
254-
if !ok {
255-
t.Error("Shutdown() should be ok")
256-
}
257-
}
258-
259244
func TestDNS_UpdateConfig(t *testing.T) {
260245
tests := []struct {
261246
name string
@@ -322,7 +307,7 @@ func stringPointer(s string) *string {
322307

323308
func newCommonDNS() *DNS {
324309
return &DNS{
325-
CheckBase: checks.CheckBase{
310+
Base: checks.Base{
326311
Mu: sync.Mutex{},
327312
DoneChan: make(chan struct{}, 1),
328313
},

pkg/checks/health/health.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ const CheckName = "health"
4848

4949
// Health is a check that measures the availability of an endpoint
5050
type Health struct {
51-
checks.CheckBase
51+
checks.Base
5252
config Config
5353
metrics metrics
5454
}
5555

5656
// NewCheck creates a new instance of the health check
5757
func NewCheck() checks.Check {
5858
return &Health{
59-
CheckBase: checks.CheckBase{
59+
Base: checks.Base{
6060
Mu: sync.Mutex{},
6161
DoneChan: make(chan struct{}, 1),
6262
},
@@ -97,12 +97,6 @@ func (h *Health) Run(ctx context.Context, cResult chan checks.ResultDTO) error {
9797
}
9898
}
9999

100-
// Shutdown is called once when the check is unregistered or sparrow shuts down
101-
func (h *Health) Shutdown() {
102-
h.DoneChan <- struct{}{}
103-
close(h.DoneChan)
104-
}
105-
106100
// UpdateConfig sets the configuration for the health check
107101
func (h *Health) UpdateConfig(cfg checks.Runtime) error {
108102
if c, ok := cfg.(*Config); ok {

pkg/checks/health/health_test.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -247,33 +247,3 @@ func TestHealth_Check(t *testing.T) {
247247
})
248248
}
249249
}
250-
251-
func TestHealth_Shutdown(t *testing.T) {
252-
cDone := make(chan struct{}, 1)
253-
c := Health{
254-
CheckBase: checks.CheckBase{
255-
DoneChan: cDone,
256-
},
257-
}
258-
c.Shutdown()
259-
260-
if _, ok := <-cDone; !ok {
261-
t.Error("Channel should be done")
262-
}
263-
264-
assert.Panics(t, func() {
265-
cDone <- struct{}{}
266-
}, "Channel is closed, should panic")
267-
268-
hc := NewCheck()
269-
hc.Shutdown()
270-
271-
_, ok := <-hc.(*Health).DoneChan
272-
if !ok {
273-
t.Error("Channel should be done")
274-
}
275-
276-
assert.Panics(t, func() {
277-
hc.(*Health).DoneChan <- struct{}{}
278-
}, "Channel is closed, should panic")
279-
}

pkg/checks/latency/latency.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ const CheckName = "latency"
4343

4444
// Latency is a check that measures the latency to an endpoint
4545
type Latency struct {
46-
checks.CheckBase
46+
checks.Base
4747
config Config
4848
metrics metrics
4949
}
5050

5151
// NewCheck creates a new instance of the latency check
5252
func NewCheck() checks.Check {
5353
return &Latency{
54-
CheckBase: checks.CheckBase{
54+
Base: checks.Base{
5555
Mu: sync.Mutex{},
5656
DoneChan: make(chan struct{}, 1),
5757
},
@@ -98,11 +98,6 @@ func (l *Latency) Run(ctx context.Context, cResult chan checks.ResultDTO) error
9898
}
9999
}
100100

101-
func (l *Latency) Shutdown() {
102-
l.DoneChan <- struct{}{}
103-
close(l.DoneChan)
104-
}
105-
106101
// UpdateConfig sets the configuration for the latency check
107102
func (l *Latency) UpdateConfig(cfg checks.Runtime) error {
108103
if c, ok := cfg.(*Config); ok {

pkg/checks/latency/latency_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -305,21 +305,6 @@ func TestLatency_check(t *testing.T) {
305305
}
306306
}
307307

308-
func TestLatency_Shutdown(t *testing.T) {
309-
cDone := make(chan struct{}, 1)
310-
c := Latency{
311-
CheckBase: checks.CheckBase{
312-
DoneChan: cDone,
313-
},
314-
}
315-
c.Shutdown()
316-
317-
_, ok := <-cDone
318-
if !ok {
319-
t.Error("Shutdown() should be ok")
320-
}
321-
}
322-
323308
func TestLatency_UpdateConfig(t *testing.T) {
324309
c := Latency{}
325310
wantCfg := Config{

pkg/checks/traceroute/check.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (t Target) String() string {
5353

5454
func NewCheck() checks.Check {
5555
c := &Traceroute{
56-
CheckBase: checks.CheckBase{
56+
Base: checks.Base{
5757
Mu: sync.Mutex{},
5858
DoneChan: make(chan struct{}, 1),
5959
},
@@ -66,7 +66,7 @@ func NewCheck() checks.Check {
6666
}
6767

6868
type Traceroute struct {
69-
checks.CheckBase
69+
checks.Base
7070
config Config
7171
traceroute tracerouteFactory
7272
metrics metrics
@@ -212,12 +212,6 @@ func (tr *Traceroute) check(ctx context.Context) map[string]result {
212212
return res
213213
}
214214

215-
// Shutdown is called once when the check is unregistered or sparrow shuts down
216-
func (tr *Traceroute) Shutdown() {
217-
tr.DoneChan <- struct{}{}
218-
close(tr.DoneChan)
219-
}
220-
221215
// UpdateConfig is called once when the check is registered
222216
// This is also called while the check is running, if the remote config is updated
223217
// This should return an error if the config is invalid

pkg/checks/traceroute/check_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func newForTest(f tracerouteFactory, maxHops int, targets []string) *Traceroute
7979
t[i] = Target{Addr: target}
8080
}
8181
return &Traceroute{
82-
CheckBase: checks.CheckBase{Mu: sync.Mutex{}, DoneChan: make(chan struct{})},
82+
Base: checks.Base{Mu: sync.Mutex{}, DoneChan: make(chan struct{})},
8383
config: Config{Targets: t, MaxHops: maxHops},
8484
traceroute: f,
8585
metrics: newMetrics(),

pkg/config/config.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ import (
2929
)
3030

3131
type Config struct {
32-
// Version is the version of the sparrow.
33-
// This is set at build time by using -ldflags "-X main.version=x.x.x"
34-
// and is not part of the configuration file or flags.
35-
Version string `yaml:"-" mapstructure:"-"`
3632
// SparrowName is the DNS name of the sparrow
3733
SparrowName string `yaml:"name" mapstructure:"name"`
3834
// Loader is the configuration for the loader

0 commit comments

Comments
 (0)