google.golang.org/grpc@v1.74.2/xds/internal/balancer/outlierdetection/subconn_wrapper.go (about)

     1  /*
     2   *
     3   * Copyright 2022 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package outlierdetection
    19  
    20  import (
    21  	"fmt"
    22  	"sync"
    23  	"sync/atomic"
    24  
    25  	"google.golang.org/grpc/balancer"
    26  	"google.golang.org/grpc/connectivity"
    27  	"google.golang.org/grpc/internal/buffer"
    28  	"google.golang.org/grpc/resolver"
    29  )
    30  
    31  // subConnWrapper wraps every created SubConn in the Outlier Detection Balancer,
    32  // to help track the latest state update from the underlying SubConn, and also
    33  // whether or not this SubConn is ejected.
    34  type subConnWrapper struct {
    35  	balancer.SubConn
    36  	// endpointInfo is a pointer to the subConnWrapper's corresponding endpoint
    37  	// map entry, if the map entry exists. It is accessed atomically.
    38  	endpointInfo atomic.Pointer[endpointInfo]
    39  	// The following fields are set during object creation and read-only after
    40  	// that.
    41  
    42  	listener func(balancer.SubConnState)
    43  
    44  	scUpdateCh *buffer.Unbounded
    45  
    46  	// The following fields are only referenced in the context of a work
    47  	// serializing buffer and don't need to be protected by a mutex.
    48  
    49  	// These two pieces of state will reach eventual consistency due to sync in
    50  	// run(), and child will always have the correctly updated SubConnState.
    51  
    52  	ejected bool
    53  
    54  	// addresses is the list of address(es) this SubConn was created with to
    55  	// help support any change in address(es)
    56  	addresses []resolver.Address
    57  	// latestHealthState is tracked to update the child policy during
    58  	// unejection.
    59  	latestHealthState balancer.SubConnState
    60  
    61  	// Access to the following fields are protected by a mutex. These fields
    62  	// should not be accessed from outside this file, instead use methods
    63  	// defined on the struct.
    64  	mu             sync.Mutex
    65  	healthListener func(balancer.SubConnState)
    66  	// latestReceivedConnectivityState is the SubConn's most recent connectivity
    67  	// state received. It may not be delivered to the child balancer yet. It is
    68  	// used to ensure a health listener is registered with the SubConn only when
    69  	// the SubConn is READY.
    70  	latestReceivedConnectivityState connectivity.State
    71  }
    72  
    73  // eject causes the wrapper to report a state update with the TRANSIENT_FAILURE
    74  // state, and to stop passing along updates from the underlying subchannel.
    75  func (scw *subConnWrapper) eject() {
    76  	scw.scUpdateCh.Put(&ejectionUpdate{
    77  		scw:       scw,
    78  		isEjected: true,
    79  	})
    80  }
    81  
    82  // uneject causes the wrapper to report a state update with the latest update
    83  // from the underlying subchannel, and resume passing along updates from the
    84  // underlying subchannel.
    85  func (scw *subConnWrapper) uneject() {
    86  	scw.scUpdateCh.Put(&ejectionUpdate{
    87  		scw:       scw,
    88  		isEjected: false,
    89  	})
    90  }
    91  
    92  func (scw *subConnWrapper) String() string {
    93  	return fmt.Sprintf("%+v", scw.addresses)
    94  }
    95  
    96  func (scw *subConnWrapper) RegisterHealthListener(listener func(balancer.SubConnState)) {
    97  	// gRPC currently supports two mechanisms that provide a health signal for
    98  	// a connection: client-side health checking and outlier detection. Earlier
    99  	// both these mechanisms signaled unhealthiness by setting the subchannel
   100  	// state to TRANSIENT_FAILURE. As part of the dualstack changes to make
   101  	// pick_first the universal leaf policy (see A61), both these mechanisms
   102  	// started using the new health listener to make health signal visible to
   103  	// the petiole policies without affecting the underlying connectivity
   104  	// management of the pick_first policy.
   105  	scw.mu.Lock()
   106  	defer scw.mu.Unlock()
   107  
   108  	if scw.latestReceivedConnectivityState != connectivity.Ready {
   109  		return
   110  	}
   111  	scw.healthListener = listener
   112  	if listener == nil {
   113  		scw.SubConn.RegisterHealthListener(nil)
   114  		return
   115  	}
   116  
   117  	scw.SubConn.RegisterHealthListener(func(scs balancer.SubConnState) {
   118  		scw.scUpdateCh.Put(&scHealthUpdate{
   119  			scw:   scw,
   120  			state: scs,
   121  		})
   122  	})
   123  }
   124  
   125  // updateSubConnHealthState stores the latest health state for unejection and
   126  // sends updates the health listener.
   127  func (scw *subConnWrapper) updateSubConnHealthState(scs balancer.SubConnState) {
   128  	scw.latestHealthState = scs
   129  	if scw.ejected {
   130  		return
   131  	}
   132  	scw.mu.Lock()
   133  	defer scw.mu.Unlock()
   134  	if scw.healthListener != nil {
   135  		scw.healthListener(scs)
   136  	}
   137  }
   138  
   139  // updateSubConnConnectivityState stores the latest connectivity state for
   140  // unejection and updates the raw connectivity listener.
   141  func (scw *subConnWrapper) updateSubConnConnectivityState(scs balancer.SubConnState) {
   142  	if scw.listener != nil {
   143  		scw.listener(scs)
   144  	}
   145  }
   146  
   147  func (scw *subConnWrapper) clearHealthListener() {
   148  	scw.mu.Lock()
   149  	defer scw.mu.Unlock()
   150  	scw.healthListener = nil
   151  }
   152  
   153  func (scw *subConnWrapper) handleUnejection() {
   154  	scw.ejected = false
   155  	// If scw.latestHealthState has never been written to will use the health
   156  	// state CONNECTING set during object creation.
   157  	scw.updateSubConnHealthState(scw.latestHealthState)
   158  }
   159  
   160  func (scw *subConnWrapper) handleEjection() {
   161  	scw.ejected = true
   162  	stateToUpdate := balancer.SubConnState{
   163  		ConnectivityState: connectivity.TransientFailure,
   164  	}
   165  	scw.mu.Lock()
   166  	defer scw.mu.Unlock()
   167  	if scw.healthListener != nil {
   168  		scw.healthListener(stateToUpdate)
   169  	}
   170  }
   171  
   172  func (scw *subConnWrapper) setLatestConnectivityState(state connectivity.State) {
   173  	scw.mu.Lock()
   174  	defer scw.mu.Unlock()
   175  	scw.latestReceivedConnectivityState = state
   176  }