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