github.com/tenywen/fabric@v1.0.0-beta.0.20170620030522-a5b1ed380643/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  	ctx, cf := context.WithCancel(context.Background())
   143  	abc, err := bc.createClient(conn).Deliver(ctx)
   144  	if err != nil {
   145  		logger.Error("Connection to ", endpoint, "established but was unable to create gRPC stream:", err)
   146  		conn.Close()
   147  		return err
   148  	}
   149  	err = bc.afterConnect(conn, abc, cf)
   150  	if err == nil {
   151  		return nil
   152  	}
   153  	// If we reached here, lets make sure connection is closed
   154  	// and nullified before we return
   155  	bc.Disconnect()
   156  	return err
   157  }
   158  
   159  func (bc *broadcastClient) afterConnect(conn *grpc.ClientConn, abc orderer.AtomicBroadcast_DeliverClient, cf context.CancelFunc) error {
   160  	bc.Lock()
   161  	bc.conn = &connection{ClientConn: conn, cancel: cf}
   162  	bc.BlocksDeliverer = abc
   163  	if bc.shouldStop() {
   164  		bc.Unlock()
   165  		return errors.New("closing")
   166  	}
   167  	bc.Unlock()
   168  	// If the client is closed at this point- before onConnect,
   169  	// any use of this object by onConnect would return an error.
   170  	err := bc.onConnect(bc)
   171  	// If the client is closed right after onConnect, but before
   172  	// the following lock- this method would return an error because
   173  	// the client has been closed.
   174  	bc.Lock()
   175  	defer bc.Unlock()
   176  	if bc.shouldStop() {
   177  		return errors.New("closing")
   178  	}
   179  	// If the client is closed right after this method exits,
   180  	// it's because this method returned nil and not an error.
   181  	// So- connect() would return nil also, and the flow of the goroutine
   182  	// is returned to doAction(), where action() is invoked - and is configured
   183  	// to check whether the client has closed or not.
   184  	if err == nil {
   185  		return nil
   186  	}
   187  	logger.Error("Failed setting up broadcast:", err)
   188  	return err
   189  }
   190  
   191  func (bc *broadcastClient) shouldStop() bool {
   192  	return atomic.LoadInt32(&bc.stopFlag) == int32(1)
   193  }
   194  
   195  // Close makes the client close its connection and shut down
   196  func (bc *broadcastClient) Close() {
   197  	bc.Lock()
   198  	defer bc.Unlock()
   199  	if bc.shouldStop() {
   200  		return
   201  	}
   202  	atomic.StoreInt32(&bc.stopFlag, int32(1))
   203  	bc.stopChan <- struct{}{}
   204  	if bc.conn == nil {
   205  		return
   206  	}
   207  	bc.conn.Close()
   208  }
   209  
   210  // Disconnect makes the client close the existing connection
   211  func (bc *broadcastClient) Disconnect() {
   212  	bc.Lock()
   213  	defer bc.Unlock()
   214  	if bc.conn == nil {
   215  		return
   216  	}
   217  	bc.conn.Close()
   218  	bc.conn = nil
   219  	bc.BlocksDeliverer = nil
   220  }
   221  
   222  type connection struct {
   223  	sync.Once
   224  	*grpc.ClientConn
   225  	cancel context.CancelFunc
   226  }
   227  
   228  func (c *connection) Close() error {
   229  	var err error
   230  	c.Once.Do(func() {
   231  		c.cancel()
   232  		err = c.ClientConn.Close()
   233  	})
   234  	return err
   235  }