@@ -4,6 +4,7 @@ package membership
44
55import (
66 "context"
7+ "crypto/sha256"
78 "encoding/base64"
89 "encoding/json"
910 "errors"
@@ -128,6 +129,14 @@ type Config struct {
128129 VeryVerbose bool
129130}
130131
132+ const (
133+ userBroadcastRetain = 8192
134+ userBroadcastBackpressure = userBroadcastRetain / 2
135+ // Match memberlist's conservative UDP buffer. User broadcasts above this
136+ // cannot fit once memberlist adds its own user-message/compound headers.
137+ userBroadcastMaxPacketBytes = 1400
138+ )
139+
131140// NewService creates a new membership service.
132141//
133142// coll, mp, and tp wire the metrics collector and OTel providers used for
@@ -207,7 +216,7 @@ func (s *Service) Start(ctx context.Context) error {
207216 mlConfig .BindAddr = s .config .BindAddr
208217 mlConfig .BindPort = s .config .BindPort
209218 mlConfig .Events = & eventDelegate {service : s }
210- mlConfig .Delegate = & delegate { service : s }
219+ mlConfig .Delegate = newDelegate ( s , mlConfig . RetransmitMult )
211220
212221 // Use custom transport if provided (for testing)
213222 if s .transport != nil {
@@ -773,7 +782,86 @@ func (ed *eventDelegate) parseNodeMeta(meta []byte) cluster.NodeMeta {
773782
774783// delegate handles memberlist delegate functions
775784type delegate struct {
776- service * Service
785+ service * Service
786+ broadcasts memberlist.TransmitLimitedQueue
787+ queuedMu sync.Mutex
788+ queued map [string ]struct {}
789+ }
790+
791+ func newDelegate (service * Service , retransmitMult int ) * delegate {
792+ if retransmitMult <= 0 {
793+ retransmitMult = 1
794+ }
795+ d := & delegate {
796+ service : service ,
797+ queued : make (map [string ]struct {}),
798+ }
799+ d .broadcasts .NumNodes = service .broadcastNodeCount
800+ d .broadcasts .RetransmitMult = retransmitMult
801+ return d
802+ }
803+
804+ func (s * Service ) broadcastNodeCount () int {
805+ if s .memberlist != nil {
806+ if n := len (s .memberlist .Members ()); n > 0 {
807+ return n
808+ }
809+ }
810+ s .mu .RLock ()
811+ n := len (s .nodes )
812+ s .mu .RUnlock ()
813+ if n > 0 {
814+ return n
815+ }
816+ return 1
817+ }
818+
819+ type muxBroadcast struct {
820+ name string
821+ msg []byte
822+ finished func (string )
823+ }
824+
825+ func (b * muxBroadcast ) Invalidates (other memberlist.Broadcast ) bool {
826+ o , ok := other .(* muxBroadcast )
827+ return ok && b .name == o .name
828+ }
829+
830+ func (b * muxBroadcast ) Name () string { return b .name }
831+ func (b * muxBroadcast ) Message () []byte {
832+ return b .msg
833+ }
834+ func (b * muxBroadcast ) Finished () {
835+ if b .finished != nil {
836+ b .finished (b .name )
837+ }
838+ }
839+
840+ var _ memberlist.NamedBroadcast = (* muxBroadcast )(nil )
841+
842+ func (d * delegate ) queueBroadcast (frame []byte ) bool {
843+ sum := sha256 .Sum256 (frame )
844+ name := string (sum [:])
845+ d .queuedMu .Lock ()
846+ if _ , exists := d .queued [name ]; exists {
847+ d .queuedMu .Unlock ()
848+ return false
849+ }
850+ d .queued [name ] = struct {}{}
851+ d .queuedMu .Unlock ()
852+
853+ d .broadcasts .QueueBroadcast (& muxBroadcast {
854+ name : name ,
855+ msg : frame ,
856+ finished : d .forgetBroadcast ,
857+ })
858+ return true
859+ }
860+
861+ func (d * delegate ) forgetBroadcast (name string ) {
862+ d .queuedMu .Lock ()
863+ delete (d .queued , name )
864+ d .queuedMu .Unlock ()
777865}
778866
779867func (d * delegate ) NodeMeta (limit int ) []byte {
@@ -828,6 +916,38 @@ func (d *delegate) NotifyMsg(data []byte) {
828916}
829917
830918func (d * delegate ) GetBroadcasts (overhead , limit int ) [][]byte {
919+ if d .service .tel != nil && d .service .tel .coll != nil {
920+ d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_calls_total" , nil )
921+ }
922+
923+ recordOut := func (out [][]byte ) {
924+ if d .service .tel == nil || d .service .tel .coll == nil || len (out ) == 0 {
925+ return
926+ }
927+ totalBytes := 0
928+ totalCost := 0
929+ for _ , frame := range out {
930+ totalBytes += len (frame )
931+ totalCost += len (frame ) + overhead
932+ }
933+ d .service .tel .coll .CounterAdd ("gossip_user_getbroadcasts_frames_total" , float64 (len (out )), nil )
934+ d .service .tel .coll .CounterAdd ("gossip_user_getbroadcasts_bytes_total" , float64 (totalBytes ), nil )
935+ if totalCost > limit {
936+ d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_overshoot_total" , nil )
937+ }
938+ }
939+
940+ queued := d .broadcasts .NumQueued ()
941+ if queued > userBroadcastRetain {
942+ d .broadcasts .Prune (userBroadcastRetain )
943+ queued = userBroadcastRetain
944+ }
945+ if queued >= userBroadcastBackpressure {
946+ out := d .broadcasts .GetBroadcasts (overhead , limit )
947+ recordOut (out )
948+ return out
949+ }
950+
831951 d .service .userDelegateMu .RLock ()
832952 dels := make ([]UserDelegate , 0 , len (d .service .userDelegates ))
833953 for _ , ud := range d .service .userDelegates {
@@ -842,25 +962,31 @@ func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
842962 // hogged the cycle).
843963 sort .Slice (dels , func (i , j int ) bool { return dels [i ].Kind () < dels [j ].Kind () })
844964
845- if d .service .tel != nil && d .service .tel .coll != nil {
846- d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_calls_total" , nil )
847- }
848-
849- var out [][]byte
850- totalBytes := 0
851- totalCost := 0
965+ queuedCost := 0
852966 muxOverhead := overhead + 5
853967
854968 wrap := func (ud UserDelegate , frames [][]byte ) {
855969 for _ , f := range frames {
970+ if uint64 (len (f )) > uint64 (^ uint32 (0 )) {
971+ if d .service .tel != nil && d .service .tel .coll != nil {
972+ d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_frame_dropped_total" , nil )
973+ }
974+ continue
975+ }
856976 wrapped := make ([]byte , 0 , len (f )+ 5 )
857977 wrapped = append (wrapped , ud .Kind ())
858978 n := uint32 (len (f ))
859979 wrapped = append (wrapped , byte (n ), byte (n >> 8 ), byte (n >> 16 ), byte (n >> 24 ))
860980 wrapped = append (wrapped , f ... )
861- totalCost += len (wrapped ) + overhead
862- totalBytes += len (wrapped )
863- out = append (out , wrapped )
981+ if len (wrapped )+ overhead > userBroadcastMaxPacketBytes {
982+ if d .service .tel != nil && d .service .tel .coll != nil {
983+ d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_frame_dropped_total" , nil )
984+ }
985+ continue
986+ }
987+ if d .queueBroadcast (wrapped ) {
988+ queuedCost += len (wrapped ) + overhead
989+ }
864990 }
865991 }
866992
@@ -883,21 +1009,19 @@ func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
8831009 // budget contract bounds emission to what fits the remainder. This
8841010 // recovers throughput when one delegate is silent.
8851011 for _ , ud := range dels {
886- remaining := limit - totalCost
1012+ remaining := limit - queuedCost
8871013 if remaining <= muxOverhead {
8881014 break
8891015 }
8901016 frames := ud .GetBroadcasts (muxOverhead , remaining )
8911017 wrap (ud , frames )
8921018 }
8931019
894- if d .service .tel != nil && d .service .tel .coll != nil && len (out ) > 0 {
895- d .service .tel .coll .CounterAdd ("gossip_user_getbroadcasts_frames_total" , float64 (len (out )), nil )
896- d .service .tel .coll .CounterAdd ("gossip_user_getbroadcasts_bytes_total" , float64 (totalBytes ), nil )
897- if totalCost > limit {
898- d .service .tel .coll .CounterInc ("gossip_user_getbroadcasts_overshoot_total" , nil )
899- }
1020+ if d .broadcasts .NumQueued () > userBroadcastRetain {
1021+ d .broadcasts .Prune (userBroadcastRetain )
9001022 }
1023+ out := d .broadcasts .GetBroadcasts (overhead , limit )
1024+ recordOut (out )
9011025 return out
9021026}
9031027
0 commit comments