@@ -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
2426type 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
3541type 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
4148type 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
5565func (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 ]
0 commit comments