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 }