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 }