github.com/NebulousLabs/Sia@v1.3.7/modules/gateway/rpc.go (about) 1 package gateway 2 3 import ( 4 "errors" 5 "sync" 6 "time" 7 8 "github.com/NebulousLabs/Sia/build" 9 "github.com/NebulousLabs/Sia/encoding" 10 "github.com/NebulousLabs/Sia/modules" 11 ) 12 13 // rpcID is an 8-byte signature that is added to all RPCs to tell the gatway 14 // what to do with the RPC. 15 type rpcID [8]byte 16 17 // String returns a string representation of an rpcID. Empty elements of rpcID 18 // will be encoded as spaces. 19 func (id rpcID) String() string { 20 for i := range id { 21 if id[i] == 0 { 22 id[i] = ' ' 23 } 24 } 25 return string(id[:]) 26 } 27 28 // handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining 29 // bytes are 0. A handlerName is specified at the beginning of each network 30 // call, indicating which function should handle the connection. 31 func handlerName(name string) (id rpcID) { 32 copy(id[:], name) 33 return 34 } 35 36 // managedRPC calls an RPC on the given address. managedRPC cannot be called on 37 // an address that the Gateway is not connected to. 38 func (g *Gateway) managedRPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { 39 g.mu.RLock() 40 peer, ok := g.peers[addr] 41 g.mu.RUnlock() 42 if !ok { 43 return errors.New("can't call RPC on unconnected peer " + string(addr)) 44 } 45 46 conn, err := peer.open() 47 if err != nil { 48 // peer probably disconnected without sending a shutdown signal; 49 // disconnect from them 50 g.log.Debugf("Could not initiate RPC with %v; disconnecting", addr) 51 peer.sess.Close() 52 g.mu.Lock() 53 delete(g.peers, addr) 54 g.mu.Unlock() 55 return err 56 } 57 defer conn.Close() 58 59 // write header 60 conn.SetDeadline(time.Now().Add(rpcStdDeadline)) 61 if err := encoding.WriteObject(conn, handlerName(name)); err != nil { 62 return err 63 } 64 conn.SetDeadline(time.Time{}) 65 // call fn 66 return fn(conn) 67 } 68 69 // RPC calls an RPC on the given address. RPC cannot be called on an address 70 // that the Gateway is not connected to. 71 func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { 72 if err := g.threads.Add(); err != nil { 73 return err 74 } 75 defer g.threads.Done() 76 return g.managedRPC(addr, name, fn) 77 } 78 79 // RegisterRPC registers an RPCFunc as a handler for a given identifier. To 80 // call an RPC, use gateway.RPC, supplying the same identifier given to 81 // RegisterRPC. Identifiers should always use PascalCase. The first 8 82 // characters of an identifier should be unique, as the identifier used 83 // internally is truncated to 8 bytes. 84 func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) { 85 g.mu.Lock() 86 defer g.mu.Unlock() 87 if _, ok := g.handlers[handlerName(name)]; ok { 88 build.Critical("RPC already registered: " + name) 89 } 90 g.handlers[handlerName(name)] = fn 91 } 92 93 // UnregisterRPC unregisters an RPC and removes the corresponding RPCFunc from 94 // g.handlers. Future calls to the RPC by peers will fail. 95 func (g *Gateway) UnregisterRPC(name string) { 96 g.mu.Lock() 97 defer g.mu.Unlock() 98 if _, ok := g.handlers[handlerName(name)]; !ok { 99 build.Critical("RPC not registered: " + name) 100 } 101 delete(g.handlers, handlerName(name)) 102 } 103 104 // RegisterConnectCall registers a name and RPCFunc to be called on a peer 105 // upon connecting. 106 func (g *Gateway) RegisterConnectCall(name string, fn modules.RPCFunc) { 107 g.mu.Lock() 108 defer g.mu.Unlock() 109 if _, ok := g.initRPCs[name]; ok { 110 build.Critical("ConnectCall already registered: " + name) 111 } 112 g.initRPCs[name] = fn 113 } 114 115 // UnregisterConnectCall unregisters an on-connect call and removes the 116 // corresponding RPCFunc from g.initRPCs. Future connections to peers will not 117 // trigger the RPC to be called on them. 118 func (g *Gateway) UnregisterConnectCall(name string) { 119 g.mu.Lock() 120 defer g.mu.Unlock() 121 if _, ok := g.initRPCs[name]; !ok { 122 build.Critical("ConnectCall not registered: " + name) 123 } 124 delete(g.initRPCs, name) 125 } 126 127 // threadedListenPeer listens for new streams on a peer connection and serves them via 128 // threadedHandleConn. 129 func (g *Gateway) threadedListenPeer(p *peer) { 130 // threadedListenPeer registers to the peerTG instead of the primary thread 131 // group because peer connections can be lifetime in length, but can also 132 // be short-lived. The fact that they can be lifetime means that they can't 133 // call threads.Add as they will block calls to threads.Flush. The fact 134 // that they can be short-lived means that threads.OnStop is not a good 135 // tool for closing out the threads. Instead, they register to peerTG, 136 // which is cleanly closed upon gateway shutdown but will not block any 137 // calls to threads.Flush() 138 if g.peerTG.Add() != nil { 139 return 140 } 141 defer g.peerTG.Done() 142 143 // Spin up a goroutine to listen for a shutdown signal from both the peer 144 // and from the gateway. In the event of either, close the session. 145 connClosedChan := make(chan struct{}) 146 peerCloseChan := make(chan struct{}) 147 go func() { 148 // Signal that the session has been successfully closed, and that this 149 // goroutine has terminated. 150 defer close(connClosedChan) 151 152 // Listen for a stop signal. 153 select { 154 case <-g.threads.StopChan(): 155 case <-peerCloseChan: 156 } 157 158 // Close the session and remove p from the peer list. 159 p.sess.Close() 160 g.mu.Lock() 161 delete(g.peers, p.NetAddress) 162 g.mu.Unlock() 163 }() 164 165 for { 166 conn, err := p.accept() 167 if err != nil { 168 g.log.Debugf("Peer connection with %v closed: %v\n", p.NetAddress, err) 169 break 170 } 171 // Set the default deadline on the conn. 172 err = conn.SetDeadline(time.Now().Add(rpcStdDeadline)) 173 if err != nil { 174 g.log.Printf("Peer connection (%v) deadline could not be set: %v\n", p.NetAddress, err) 175 continue 176 } 177 178 // The handler is responsible for closing the connection, though a 179 // default deadline has been set. 180 go g.threadedHandleConn(conn) 181 if !g.managedSleep(peerRPCDelay) { 182 break 183 } 184 } 185 // Signal that the goroutine can shutdown. 186 close(peerCloseChan) 187 // Wait for confirmation that the goroutine has shut down before returning 188 // and releasing the threadgroup registration. 189 <-connClosedChan 190 } 191 192 // threadedHandleConn reads header data from a connection, then routes it to the 193 // appropriate handler for further processing. 194 func (g *Gateway) threadedHandleConn(conn modules.PeerConn) { 195 defer conn.Close() 196 if g.threads.Add() != nil { 197 return 198 } 199 defer g.threads.Done() 200 201 var id rpcID 202 err := conn.SetDeadline(time.Now().Add(rpcStdDeadline)) 203 if err != nil { 204 return 205 } 206 if err := encoding.ReadObject(conn, &id, 8); err != nil { 207 return 208 } 209 // call registered handler for this ID 210 g.mu.RLock() 211 fn, ok := g.handlers[id] 212 g.mu.RUnlock() 213 if !ok { 214 g.log.Debugf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RPCAddr(), id) 215 return 216 } 217 g.log.Debugf("INFO: incoming conn %v requested RPC \"%v\"", conn.RPCAddr(), id) 218 219 // call fn 220 err = fn(conn) 221 // don't log benign errors 222 if err == modules.ErrDuplicateTransactionSet || err == modules.ErrBlockKnown { 223 err = nil 224 } 225 if err != nil { 226 g.log.Debugf("WARN: incoming RPC \"%v\" from conn %v failed: %v", id, conn.RPCAddr(), err) 227 } 228 } 229 230 // Broadcast calls an RPC on all of the specified peers. The calls are run in 231 // parallel. Broadcasts are restricted to "one-way" RPCs, which simply write an 232 // object and disconnect. This is why Broadcast takes an interface{} instead of 233 // an RPCFunc. 234 func (g *Gateway) Broadcast(name string, obj interface{}, peers []modules.Peer) { 235 if g.threads.Add() != nil { 236 return 237 } 238 defer g.threads.Done() 239 240 g.log.Debugf("INFO: broadcasting RPC %q to %v peers", name, len(peers)) 241 242 // only encode obj once, instead of using WriteObject 243 enc := encoding.Marshal(obj) 244 fn := func(conn modules.PeerConn) error { 245 return encoding.WritePrefixedBytes(conn, enc) 246 } 247 248 var wg sync.WaitGroup 249 for _, p := range peers { 250 wg.Add(1) 251 go func(addr modules.NetAddress) { 252 defer wg.Done() 253 err := g.managedRPC(addr, name, fn) 254 if err != nil { 255 g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed (attempting again in 10 seconds): %v", name, addr, err) 256 // try one more time before giving up 257 select { 258 case <-time.After(10 * time.Second): 259 case <-g.threads.StopChan(): 260 return 261 } 262 err := g.managedRPC(addr, name, fn) 263 if err != nil { 264 g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed twice: %v", name, addr, err) 265 } 266 } 267 }(p.NetAddress) 268 } 269 wg.Wait() 270 }