Skip to content

Commit d84c843

Browse files
committed
Wait for a backend + timeout
1 parent 5e3f162 commit d84c843

5 files changed

Lines changed: 69 additions & 24 deletions

File tree

balancer/wrr.go

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"math/rand"
77
"slices"
88
"sync"
9+
"time"
910

1011
"github.com/hashicorp/hcl/v2"
1112
"github.com/hashicorp/hcl/v2/gohcl"
1213
"github.com/rs/zerolog"
1314
"github.com/rs/zerolog/log"
1415
"github.com/zclconf/go-cty/cty"
16+
"golang.org/x/sync/semaphore"
1517

1618
"mlb/backend"
1719
"mlb/misc"
@@ -22,20 +24,25 @@ func init() {
2224
}
2325

2426
type WRRBalancer struct {
25-
id string
26-
backends backend.BackendsMap
27-
weightedlist []string
28-
mu sync.RWMutex
29-
log zerolog.Logger
30-
upd_chan chan backend.BackendUpdate
31-
source string
32-
evalCtx *hcl.EvalContext
27+
id string
28+
backends backend.BackendsMap
29+
weightedlist []string
30+
mu sync.RWMutex
31+
log zerolog.Logger
32+
upd_chan chan backend.BackendUpdate
33+
source string
34+
evalCtx *hcl.EvalContext
35+
ctx context.Context
36+
ctx_cancel context.CancelFunc
37+
wait_backends *semaphore.Weighted
38+
timeout time.Duration
3339
}
3440

3541
type WRRBalancerConfig struct {
36-
ID string
37-
Source string `hcl:"source"`
38-
Weight hcl.Expression `hcl:"weight"`
42+
ID string
43+
Source string `hcl:"source"`
44+
Weight hcl.Expression `hcl:"weight"`
45+
Timeout string `hcl:"timeout,optional"`
3946
}
4047

4148
type WRRBalancerFactory struct{}
@@ -49,38 +56,53 @@ func (w WRRBalancerFactory) parseConfig(tc *Config) *WRRBalancerConfig {
4956
config := &WRRBalancerConfig{}
5057
gohcl.DecodeBody(tc.Config, tc.ctx, config)
5158
config.ID = fmt.Sprintf("balancer.%s.%s", tc.Type, tc.Name)
59+
if config.Timeout == "" {
60+
config.Timeout = "0s"
61+
}
5262
return config
5363
}
5464

5565
func (w WRRBalancerFactory) New(tc *Config, wg *sync.WaitGroup, ctx context.Context) backend.BackendProvider {
5666
config := w.parseConfig(tc)
5767

5868
b := &WRRBalancer{
59-
id: config.ID,
60-
backends: make(backend.BackendsMap),
61-
weightedlist: make([]string, 0),
62-
log: log.With().Str("id", config.ID).Logger(),
63-
upd_chan: make(chan backend.BackendUpdate),
64-
source: config.Source,
65-
evalCtx: tc.ctx,
69+
id: config.ID,
70+
backends: make(backend.BackendsMap),
71+
weightedlist: make([]string, 0),
72+
log: log.With().Str("id", config.ID).Logger(),
73+
upd_chan: make(chan backend.BackendUpdate),
74+
source: config.Source,
75+
evalCtx: tc.ctx,
76+
wait_backends: semaphore.NewWeighted(1),
6677
}
6778

68-
ctx, cancel := context.WithCancel(ctx)
79+
var err error
80+
81+
b.timeout, err = time.ParseDuration(config.Timeout)
82+
misc.PanicIfErr(err)
83+
84+
b.ctx, b.ctx_cancel = context.WithCancel(ctx)
6985

7086
wg.Add(1)
7187
b.log.Info().Msg("WRR Balancer starting")
7288

7389
go func() {
7490
defer wg.Done()
7591
defer b.log.Info().Msg("WRR Balancer stopped")
76-
defer cancel()
92+
defer b.ctx_cancel()
7793
defer close(b.upd_chan)
7894

95+
b.log.Debug().Msg("No backends in the list, acquiring the lock")
96+
b.wait_backends.Acquire(b.ctx, 1)
97+
7998
mainloop:
8099
for {
81100
select {
82101
case upd := <-b.upd_chan: // Backend changed
83102
b.mu.Lock()
103+
104+
list_previous_size := len(b.weightedlist)
105+
84106
switch upd.Kind {
85107
case backend.UpdBackendAdded:
86108
var weight int
@@ -114,9 +136,20 @@ func (w WRRBalancerFactory) New(tc *Config, wg *sync.WaitGroup, ctx context.Cont
114136
b.weightedlist = slices.DeleteFunc(b.weightedlist, func(a string) bool { return a == upd.Address })
115137
delete(b.backends, upd.Address)
116138
}
139+
140+
list_new_size := len(b.weightedlist)
141+
142+
if list_previous_size == 0 && list_new_size > 0 {
143+
b.log.Debug().Msg("At least one backend has been added to the list, releasing the lock")
144+
b.wait_backends.Release(1)
145+
} else if list_previous_size > 0 && list_new_size == 0 {
146+
b.log.Debug().Msg("There are no more backends in the list, acquiring the lock")
147+
b.wait_backends.Acquire(b.ctx, 1)
148+
}
149+
117150
b.mu.Unlock()
118151

119-
case <-ctx.Done(): // Context cancelled
152+
case <-b.ctx.Done(): // Context cancelled
120153
break mainloop
121154
}
122155
}
@@ -129,6 +162,17 @@ func (b *WRRBalancer) GetBackend() *backend.Backend {
129162
b.mu.RLock()
130163
defer b.mu.RUnlock()
131164

165+
// Wait for the backend list to be populated or a timeout to occur
166+
if len(b.weightedlist) == 0 && b.timeout > 0 {
167+
b.mu.RUnlock()
168+
ctx, ctx_cancel := context.WithDeadline(b.ctx, time.Now().Add(b.timeout))
169+
defer ctx_cancel()
170+
if b.wait_backends.Acquire(ctx, 1) == nil {
171+
b.wait_backends.Release(1)
172+
}
173+
b.mu.RLock()
174+
}
175+
132176
if len(b.weightedlist) > 0 {
133177
address := b.weightedlist[rand.Intn(len(b.weightedlist))]
134178
return b.backends[address]

config.example.hcl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ backends_processor "simple_filter" "mysql_main_ro" {
5353
balancer "wrr" "mysql_main_ro" {
5454
source = backends_processor.simple_filter.mysql_main_ro
5555
weight = backend.meta.consul.weight
56+
timeout = "1s"
5657
}
5758

5859
proxy "tcp" "mysql_main_ro" {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/prometheus/client_golang v1.16.0
99
github.com/rs/zerolog v1.30.0
1010
golang.org/x/sys v0.11.0
11+
golang.org/x/sync v0.3.0
1112
)
1213

1314
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N0
6767
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
6868
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
6969
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
70+
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
71+
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
7072
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7173
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7274
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

main.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ func main() {
119119

120120
metrics.NewHTTPServer(conf.Metrics.Address, &wg, ctx)
121121

122-
// TODO: Replace that with a real retroaction from backend providers
123-
time.Sleep(1 * time.Second) // Wait one second to ensure backends are available
124-
125122
// Start proxies
126123
for _, c := range conf.ProxyList {
127124
proxy.New(c, backendProviders, &wg, ctx)

0 commit comments

Comments
 (0)