github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/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  	start := time.Now()
    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, time.Since(start))
   100  			if !retry {
   101  				break
   102  			}
   103  			bc.sleep(backoffDuration)
   104  			continue
   105  		}
   106  		return resp, nil
   107  	}
   108  	if bc.shouldStop() {
   109  		return nil, errors.New("Client is closing")
   110  	}
   111  	return nil, fmt.Errorf("Attempts (%d) or elapsed time (%v) exhausted", attempt, time.Since(start))
   112  }
   113  
   114  func (bc *broadcastClient) doAction(action func() (interface{}, error)) (interface{}, error) {
   115  	if bc.conn == nil {
   116  		err := bc.connect()
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	resp, err := action()
   122  	if err != nil {
   123  		bc.disconnect()
   124  		return nil, err
   125  	}
   126  	return resp, nil
   127  }
   128  
   129  func (bc *broadcastClient) sleep(duration time.Duration) {
   130  	select {
   131  	case <-time.After(duration):
   132  	case <-bc.stopChan:
   133  	}
   134  }
   135  
   136  func (bc *broadcastClient) connect() error {
   137  	conn, endpoint, err := bc.prod.NewConnection()
   138  	if err != nil {
   139  		logger.Error("Failed obtaining connection:", err)
   140  		return err
   141  	}
   142  	abc, err := bc.createClient(conn).Deliver(context.Background())
   143  	if err != nil {
   144  		logger.Error("Connection to ", endpoint, "established but was unable to create gRPC stream:", err)
   145  		conn.Close()
   146  		return err
   147  	}
   148  	err = bc.afterConnect(conn, abc)
   149  	if err == nil {
   150  		return nil
   151  	}
   152  	// If we reached here, lets make sure connection is closed
   153  	// and nullified before we return
   154  	bc.disconnect()
   155  	return err
   156  }
   157  
   158  func (bc *broadcastClient) afterConnect(conn *grpc.ClientConn, abc orderer.AtomicBroadcast_DeliverClient) error {
   159  	bc.Lock()
   160  	bc.conn = &connection{ClientConn: conn}
   161  	bc.BlocksDeliverer = abc
   162  	if bc.shouldStop() {
   163  		bc.Unlock()
   164  		return errors.New("closing")
   165  	}
   166  	bc.Unlock()
   167  	// If the client is closed at this point- before onConnect,
   168  	// any use of this object by onConnect would return an error.
   169  	err := bc.onConnect(bc)
   170  	// If the client is closed right after onConnect, but before
   171  	// the following lock- this method would return an error because
   172  	// the client has been closed.
   173  	bc.Lock()
   174  	defer bc.Unlock()
   175  	if bc.shouldStop() {
   176  		return errors.New("closing")
   177  	}
   178  	// If the client is closed right after this method exits,
   179  	// it's because this method returned nil and not an error.
   180  	// So- connect() would return nil also, and the flow of the goroutine
   181  	// is returned to doAction(), where action() is invoked - and is configured
   182  	// to check whether the client has closed or not.
   183  	if err == nil {
   184  		return nil
   185  	}
   186  	logger.Error("Failed setting up broadcast:", err)
   187  	return err
   188  }
   189  
   190  func (bc *broadcastClient) shouldStop() bool {
   191  	return atomic.LoadInt32(&bc.stopFlag) == int32(1)
   192  }
   193  
   194  func (bc *broadcastClient) Close() {
   195  	bc.Lock()
   196  	defer bc.Unlock()
   197  	if bc.shouldStop() {
   198  		return
   199  	}
   200  	atomic.StoreInt32(&bc.stopFlag, int32(1))
   201  	bc.stopChan <- struct{}{}
   202  	if bc.conn == nil {
   203  		return
   204  	}
   205  	bc.conn.Close()
   206  }
   207  
   208  func (bc *broadcastClient) disconnect() {
   209  	bc.Lock()
   210  	defer bc.Unlock()
   211  	if bc.conn == nil {
   212  		return
   213  	}
   214  	bc.conn.Close()
   215  	bc.conn = nil
   216  	bc.BlocksDeliverer = nil
   217  }
   218  
   219  type connection struct {
   220  	*grpc.ClientConn
   221  	sync.Once
   222  }
   223  
   224  func (c *connection) Close() error {
   225  	var err error
   226  	c.Once.Do(func() {
   227  		err = c.ClientConn.Close()
   228  	})
   229  	return err
   230  }