@@ -45,17 +45,14 @@ func (s *subBalancerState) String() string {
4545type  balancerStateAggregator  struct  {
4646	cc      balancer.ClientConn 
4747	logger  * grpclog.PrefixLogger 
48+ 	csEval  * balancer.ConnectivityStateEvaluator 
4849
4950	mu  sync.Mutex 
50- 	// If started is false, no updates should be sent to the parent cc. A closed 
51- 	// sub-balancer could still send pickers to this aggregator. This makes sure 
52- 	// that no updates will be forwarded to parent when the whole balancer group 
53- 	// and states aggregator is closed. 
54- 	started  bool 
55- 	// All balancer IDs exist as keys in this map, even if balancer group is not 
56- 	// started. 
57- 	// 
58- 	// If an ID is not in map, it's either removed or never added. 
51+ 	// This field is used to ensure that no updates are forwarded to the parent 
52+ 	// CC once the aggregator is closed. A closed sub-balancer could still send 
53+ 	// pickers to this aggregator. 
54+ 	closed  bool 
55+ 	// Map from child policy name to last reported state. 
5956	idToPickerState  map [string ]* subBalancerState 
6057	// Set when UpdateState call propagation is paused. 
6158	pauseUpdateState  bool 
@@ -68,34 +65,24 @@ func newBalancerStateAggregator(cc balancer.ClientConn, logger *grpclog.PrefixLo
6865	return  & balancerStateAggregator {
6966		cc :              cc ,
7067		logger :          logger ,
68+ 		csEval :          & balancer.ConnectivityStateEvaluator {},
7169		idToPickerState : make (map [string ]* subBalancerState ),
7270	}
7371}
7472
75- // Start starts the aggregator. It can be called after Close to restart the 
76- // aggretator. 
77- func  (bsa  * balancerStateAggregator ) start () {
78- 	bsa .mu .Lock ()
79- 	defer  bsa .mu .Unlock ()
80- 	bsa .started  =  true 
81- }
82- 
83- // Close closes the aggregator. When the aggregator is closed, it won't call 
84- // parent ClientConn to update balancer state. 
8573func  (bsa  * balancerStateAggregator ) close () {
8674	bsa .mu .Lock ()
8775	defer  bsa .mu .Unlock ()
88- 	bsa .started  =  false 
89- 	bsa .clearStates ()
76+ 	bsa .closed  =  true 
9077}
9178
92- // add adds a sub-balancer state with weight. It adds a place holder, and waits 
93- // for the real sub-balancer to update state. 
79+ // add adds a sub-balancer in CONNECTING state. 
9480// 
9581// This is called when there's a new child. 
9682func  (bsa  * balancerStateAggregator ) add (id  string ) {
9783	bsa .mu .Lock ()
9884	defer  bsa .mu .Unlock ()
85+ 
9986	bsa .idToPickerState [id ] =  & subBalancerState {
10087		// Start everything in CONNECTING, so if one of the sub-balancers 
10188		// reports TransientFailure, the RPCs will still wait for the other 
@@ -106,6 +93,8 @@ func (bsa *balancerStateAggregator) add(id string) {
10693		},
10794		stateToAggregate : connectivity .Connecting ,
10895	}
96+ 	bsa .csEval .RecordTransition (connectivity .Shutdown , connectivity .Connecting )
97+ 	bsa .buildAndUpdateLocked ()
10998}
11099
111100// remove removes the sub-balancer state. Future updates from this sub-balancer, 
@@ -118,9 +107,15 @@ func (bsa *balancerStateAggregator) remove(id string) {
118107	if  _ , ok  :=  bsa .idToPickerState [id ]; ! ok  {
119108		return 
120109	}
110+ 	// Setting the state of the deleted sub-balancer to Shutdown will get 
111+ 	// csEvltr to remove the previous state for any aggregated state 
112+ 	// evaluations. Transitions to and from connectivity.Shutdown are ignored 
113+ 	// by csEvltr. 
114+ 	bsa .csEval .RecordTransition (bsa .idToPickerState [id ].stateToAggregate , connectivity .Shutdown )
121115	// Remove id and picker from picker map. This also results in future updates 
122116	// for this ID to be ignored. 
123117	delete (bsa .idToPickerState , id )
118+ 	bsa .buildAndUpdateLocked ()
124119}
125120
126121// pauseStateUpdates causes UpdateState calls to not propagate to the parent 
@@ -140,7 +135,7 @@ func (bsa *balancerStateAggregator) resumeStateUpdates() {
140135	defer  bsa .mu .Unlock ()
141136	bsa .pauseUpdateState  =  false 
142137	if  bsa .needUpdateStateOnResume  {
143- 		bsa .cc .UpdateState (bsa .build ())
138+ 		bsa .cc .UpdateState (bsa .buildLocked ())
144139	}
145140}
146141
@@ -149,6 +144,8 @@ func (bsa *balancerStateAggregator) resumeStateUpdates() {
149144// 
150145// It calls parent ClientConn's UpdateState with the new aggregated state. 
151146func  (bsa  * balancerStateAggregator ) UpdateState (id  string , state  balancer.State ) {
147+ 	bsa .logger .Infof ("State update from sub-balancer %q: %+v" , id , state )
148+ 
152149	bsa .mu .Lock ()
153150	defer  bsa .mu .Unlock ()
154151	pickerSt , ok  :=  bsa .idToPickerState [id ]
@@ -162,42 +159,17 @@ func (bsa *balancerStateAggregator) UpdateState(id string, state balancer.State)
162159		// update the state, to prevent the aggregated state from being always 
163160		// CONNECTING. Otherwise, stateToAggregate is the same as 
164161		// state.ConnectivityState. 
162+ 		bsa .csEval .RecordTransition (pickerSt .stateToAggregate , state .ConnectivityState )
165163		pickerSt .stateToAggregate  =  state .ConnectivityState 
166164	}
167165	pickerSt .state  =  state 
168- 
169- 	if  ! bsa .started  {
170- 		return 
171- 	}
172- 	if  bsa .pauseUpdateState  {
173- 		// If updates are paused, do not call UpdateState, but remember that we 
174- 		// need to call it when they are resumed. 
175- 		bsa .needUpdateStateOnResume  =  true 
176- 		return 
177- 	}
178- 	bsa .cc .UpdateState (bsa .build ())
179- }
180- 
181- // clearState Reset everything to init state (Connecting) but keep the entry in 
182- // map (to keep the weight). 
183- // 
184- // Caller must hold bsa.mu. 
185- func  (bsa  * balancerStateAggregator ) clearStates () {
186- 	for  _ , pState  :=  range  bsa .idToPickerState  {
187- 		pState .state  =  balancer.State {
188- 			ConnectivityState : connectivity .Connecting ,
189- 			Picker :            base .NewErrPicker (balancer .ErrNoSubConnAvailable ),
190- 		}
191- 		pState .stateToAggregate  =  connectivity .Connecting 
192- 	}
166+ 	bsa .buildAndUpdateLocked ()
193167}
194168
195- // buildAndUpdate combines the sub-state from each sub-balancer into one state, 
196- // and update it to parent ClientConn. 
197- func  (bsa  * balancerStateAggregator ) buildAndUpdate () {
198- 	bsa .mu .Lock ()
199- 	defer  bsa .mu .Unlock ()
200- 	if  ! bsa .started  {
169+ // buildAndUpdateLocked combines the sub-state from each sub-balancer into one 
170+ // state, and sends a picker update to the parent ClientConn. 
171+ func  (bsa  * balancerStateAggregator ) buildAndUpdateLocked () {
172+ 	if  bsa .closed  {
201173		return 
202174	}
203175	if  bsa .pauseUpdateState  {
@@ -206,55 +178,19 @@ func (bsa *balancerStateAggregator) buildAndUpdate() {
206178		bsa .needUpdateStateOnResume  =  true 
207179		return 
208180	}
209- 	bsa .cc .UpdateState (bsa .build ())
181+ 	bsa .cc .UpdateState (bsa .buildLocked ())
210182}
211183
212- // build combines sub-states into one. The picker will do a child pick. 
213- // 
214- // Caller must hold bsa.mu. 
215- func  (bsa  * balancerStateAggregator ) build () balancer.State  {
216- 	// TODO: the majority of this function (and UpdateState) is exactly the same 
217- 	// as weighted_target's state aggregator. Try to make a general utility 
218- 	// function/struct to handle the logic. 
219- 	// 
220- 	// One option: make a SubBalancerState that handles Update(State), including 
221- 	// handling the special connecting after ready, as in UpdateState(). Then a 
222- 	// function to calculate the aggregated connectivity state as in this 
223- 	// function. 
224- 	// 
225- 	// TODO: use balancer.ConnectivityStateEvaluator to calculate the aggregated 
226- 	// state. 
227- 	var  readyN , connectingN , idleN  int 
228- 	for  _ , ps  :=  range  bsa .idToPickerState  {
229- 		switch  ps .stateToAggregate  {
230- 		case  connectivity .Ready :
231- 			readyN ++ 
232- 		case  connectivity .Connecting :
233- 			connectingN ++ 
234- 		case  connectivity .Idle :
235- 			idleN ++ 
236- 		}
237- 	}
238- 	var  aggregatedState  connectivity.State 
239- 	switch  {
240- 	case  readyN  >  0 :
241- 		aggregatedState  =  connectivity .Ready 
242- 	case  connectingN  >  0 :
243- 		aggregatedState  =  connectivity .Connecting 
244- 	case  idleN  >  0 :
245- 		aggregatedState  =  connectivity .Idle 
246- 	default :
247- 		aggregatedState  =  connectivity .TransientFailure 
248- 	}
249- 
184+ // buildLocked combines sub-states into one. 
185+ func  (bsa  * balancerStateAggregator ) buildLocked () balancer.State  {
250186	// The picker's return error might not be consistent with the 
251187	// aggregatedState. Because for this LB policy, we want to always build 
252188	// picker with all sub-pickers (not only ready sub-pickers), so even if the 
253189	// overall state is Ready, pick for certain RPCs can behave like Connecting 
254190	// or TransientFailure. 
255191	bsa .logger .Infof ("Child pickers: %+v" , bsa .idToPickerState )
256192	return  balancer.State {
257- 		ConnectivityState : aggregatedState ,
193+ 		ConnectivityState : bsa . csEval . CurrentState () ,
258194		Picker :            newPickerGroup (bsa .idToPickerState ),
259195	}
260196}
0 commit comments