github.com/darrenli6/fabric-sdk-example@v0.0.0-20220109053535-94b13b56df8c/core/deliverservice/client.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8                   http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package deliverclient
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/hyperledger/fabric/core/comm"
    27  	"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
    28  	"github.com/hyperledger/fabric/protos/common"
    29  	"github.com/hyperledger/fabric/protos/orderer"
    30  	"golang.org/x/net/context"
    31  	"google.golang.org/grpc"
    32  )
    33  
    34  // broadcastSetup is a function that is called by the broadcastClient immediately after each
    35  // successful connection to the ordering service
    36  type broadcastSetup func(blocksprovider.BlocksDeliverer) error
    37  
    38  // retryPolicy receives as parameters the number of times the attempt has failed
    39  // and a duration that specifies the total elapsed time passed since the first attempt.
    40  // If further attempts should be made, it returns:
    41  // 	- a time duration after which the next attempt would be made, true
    42  // Else, a zero duration, false
    43  type retryPolicy func(attemptNum int, elapsedTime time.Duration) (time.Duration, bool)
    44  
    45  // clientFactory creates a gRPC broadcast client out of a ClientConn
    46  type clientFactory func(*grpc.ClientConn) orderer.AtomicBroadcastClient
    47  
    48  type broadcastClient struct {
    49  	stopFlag int32
    50  	sync.Mutex
    51  	stopChan     chan struct{}
    52  	createClient clientFactory
    53  	shouldRetry  retryPolicy
    54  	onConnect    broadcastSetup
    55  	prod         comm.ConnectionProducer
    56  	blocksprovider.BlocksDeliverer
    57  	conn *connection
    58  }
    59  
    60  // NewBroadcastClient returns a broadcastClient with the given params
    61  func NewBroadcastClient(prod comm.ConnectionProducer, clFactory clientFactory, onConnect broadcastSetup, bos retryPolicy) *broadcastClient {
    62  	return &broadcastClient{prod: prod, onConnect: onConnect, shouldRetry: bos, createClient: clFactory, stopChan: make(chan struct{}, 1)}
    63  }
    64  
    65  // Recv receives a message from the ordering service
    66  func (bc *broadcastClient) Recv() (*orderer.DeliverResponse, error) {
    67  	o, err := bc.try(func() (interface{}, error) {
    68  		if bc.shouldStop() {
    69  			return nil, errors.New("closing")
    70  		}
    71  		return bc.BlocksDeliverer.Recv()
    72  	})
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return o.(*orderer.DeliverResponse), nil
    77  }
    78  
    79  // Send sends a message to the ordering service
    80  func (bc *broadcastClient) Send(msg *common.Envelope) error {
    81  	_, err := bc.try(func() (interface{}, error) {
    82  		if bc.shouldStop() {
    83  			return nil, errors.New("closing")
    84  		}
    85  		return nil, bc.BlocksDeliverer.Send(msg)
    86  	})
    87  	return err
    88  }
    89  
    90  func (bc *broadcastClient) try(action func() (interface{}, error)) (interface{}, error) {
    91  	attempt := 0
    92  	var totalRetryTime time.Duration
    93  	var backoffDuration time.Duration
    94  	retry := true
    95  	for retry && !bc.shouldStop() {
    96  		attempt++
    97  		resp, err := bc.doAction(action)
    98  		if err != nil {
    99  			backoffDuration, retry = bc.shouldRetry(attempt, totalRetryTime)
   100  			if !retry {
   101  				logger.Warning("Got error:", err, "at", attempt, "attempt. Ceasing to retry")
   102  				break
   103  			}
   104  			logger.Warning("Got error:", err, ", at", attempt, "attempt. Retrying in", backoffDuration)
   105  			totalRetryTime += backoffDuration
   106  			bc.sleep(backoffDuration)
   107  			continue
   108  		}
   109  		return resp, nil
   110  	}
   111  	if bc.shouldStop() {
   112  		return nil, errors.New("Client is closing")
   113  	}
   114  	return nil, fmt.Errorf("Attempts (%d) or elapsed time (%v) exhausted", attempt, totalRetryTime)
   115  }
   116  
   117  func (bc *broadcastClient) doAction(action func() (interface{}, error)) (interface{}, error) {
   118  	if bc.conn == nil {
   119  		err := bc.connect()
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  	}
   124  	resp, err := action()
   125  	if err != nil {
   126  		bc.Disconnect()
   127  		return nil, err
   128  	}
   129  	return resp, nil
   130  }
   131  
   132  func (bc *broadcastClient) sleep(duration time.Duration) {
   133  	select {
   134  	case <-time.After(duration):
   135  	case <-bc.stopChan:
   136  	}
   137  }
   138  
   139  func (bc *broadcastClient) connect() error {
   140  	conn, endpoint, err := bc.prod.NewConnection()
   141  	logger.Debug("Connected to", endpoint)
   142  	if err != nil {
   143  		logger.Error("Failed obtaining connection:", err)
   144  		return err
   145  	}
   146  	ctx, cf := context.WithCancel(context.Background())
   147  	logger.Debug("Establishing gRPC stream with", endpoint, "...")
   148  	abc, err := bc.createClient(conn).Deliver(ctx)
   149  	if err != nil {
   150  		logger.Error("Connection to ", endpoint, "established but was unable to create gRPC stream:", err)
   151  		conn.Close()
   152  		return err
   153  	}
   154  	err = bc.afterConnect(conn, abc, cf)
   155  	if err == nil {
   156  		return nil
   157  	}
   158  	logger.Warning("Failed running post-connection procedures:", err)
   159  	// If we reached here, lets make sure connection is closed
   160  	// and nullified before we return
   161  	bc.Disconnect()
   162  	return err
   163  }
   164  
   165  func (bc *broadcastClient) afterConnect(conn *grpc.ClientConn, abc orderer.AtomicBroadcast_DeliverClient, cf context.CancelFunc) error {
   166  	logger.Debug("Entering")
   167  	defer logger.Debug("Exiting")
   168  	bc.Lock()
   169  	bc.conn = &connection{ClientConn: conn, cancel: cf}
   170  	bc.BlocksDeliverer = abc
   171  	if bc.shouldStop() {
   172  		bc.Unlock()
   173  		return errors.New("closing")
   174  	}
   175  	bc.Unlock()
   176  	// If the client is closed at this point- before onConnect,
   177  	// any use of this object by onConnect would return an error.
   178  	err := bc.onConnect(bc)
   179  	// If the client is closed right after onConnect, but before
   180  	// the following lock- this method would return an error because
   181  	// the client has been closed.
   182  	bc.Lock()
   183  	defer bc.Unlock()
   184  	if bc.shouldStop() {
   185  		return errors.New("closing")
   186  	}
   187  	// If the client is closed right after this method exits,
   188  	// it's because this method returned nil and not an error.
   189  	// So- connect() would return nil also, and the flow of the goroutine
   190  	// is returned to doAction(), where action() is invoked - and is configured
   191  	// to check whether the client has closed or not.
   192  	if err == nil {
   193  		return nil
   194  	}
   195  	logger.Error("Failed setting up broadcast:", err)
   196  	return err
   197  }
   198  
   199  func (bc *broadcastClient) shouldStop() bool {
   200  	return atomic.LoadInt32(&bc.stopFlag) == int32(1)
   201  }
   202  
   203  // Close makes the client close its connection and shut down
   204  func (bc *broadcastClient) Close() {
   205  	logger.Debug("Entering")
   206  	defer logger.Debug("Exiting")
   207  	bc.Lock()
   208  	defer bc.Unlock()
   209  	if bc.shouldStop() {
   210  		return
   211  	}
   212  	atomic.StoreInt32(&bc.stopFlag, int32(1))
   213  	bc.stopChan <- struct{}{}
   214  	if bc.conn == nil {
   215  		return
   216  	}
   217  	bc.conn.Close()
   218  }
   219  
   220  // Disconnect makes the client close the existing connection
   221  func (bc *broadcastClient) Disconnect() {
   222  	logger.Debug("Entering")
   223  	defer logger.Debug("Exiting")
   224  	bc.Lock()
   225  	defer bc.Unlock()
   226  	if bc.conn == nil {
   227  		return
   228  	}
   229  	bc.conn.Close()
   230  	bc.conn = nil
   231  	bc.BlocksDeliverer = nil
   232  }
   233  
   234  type connection struct {
   235  	sync.Once
   236  	*grpc.ClientConn
   237  	cancel context.CancelFunc
   238  }
   239  
   240  func (c *connection) Close() error {
   241  	var err error
   242  	c.Once.Do(func() {
   243  		c.cancel()
   244  		err = c.ClientConn.Close()
   245  	})
   246  	return err
   247  }