github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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 type rpcID [8]byte 14 15 func (id rpcID) String() string { 16 for i := range id { 17 if id[i] == 0 { 18 id[i] = ' ' 19 } 20 } 21 return string(id[:]) 22 } 23 24 // handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining 25 // bytes are 0. A handlerName is specified at the beginning of each network 26 // call, indicating which function should handle the connection. 27 func handlerName(name string) (id rpcID) { 28 copy(id[:], name) 29 return 30 } 31 32 // RPC calls an RPC on the given address. RPC cannot be called on an address 33 // that the Gateway is not connected to. 34 func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { 35 id := g.mu.RLock() 36 peer, ok := g.peers[addr] 37 g.mu.RUnlock(id) 38 if !ok { 39 return errors.New("can't call RPC on unconnected peer " + string(addr)) 40 } 41 42 conn, err := peer.open() 43 if err != nil { 44 return err 45 } 46 defer conn.Close() 47 48 // write header 49 if err := encoding.WriteObject(conn, handlerName(name)); err != nil { 50 return err 51 } 52 // call fn 53 return fn(conn) 54 } 55 56 // RegisterRPC registers an RPCFunc as a handler for a given identifier. To 57 // call an RPC, use gateway.RPC, supplying the same identifier given to 58 // RegisterRPC. Identifiers should always use PascalCase. The first 8 59 // characters of an identifier should be unique, as the identifier used 60 // internally is truncated to 8 bytes. 61 func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) { 62 id := g.mu.Lock() 63 defer g.mu.Unlock(id) 64 if build.DEBUG && build.Release != "testing" { 65 if _, ok := g.handlers[handlerName(name)]; ok { 66 panic("refusing to overwrite RPC " + name) 67 } 68 } 69 g.handlers[handlerName(name)] = fn 70 } 71 72 // RegisterConnectCall registers a name and RPCFunc to be called on a peer 73 // upon connecting. 74 func (g *Gateway) RegisterConnectCall(name string, fn modules.RPCFunc) { 75 id := g.mu.Lock() 76 defer g.mu.Unlock(id) 77 if build.DEBUG { 78 if _, ok := g.initRPCs[name]; ok { 79 panic("refusing to overwrite RPC " + name) 80 } 81 } 82 g.initRPCs[name] = fn 83 } 84 85 // listenPeer listens for new streams on a peer connection and serves them via 86 // threadedHandleConn. 87 func (g *Gateway) listenPeer(p *peer) { 88 for { 89 conn, err := p.accept() 90 if err != nil { 91 g.log.Println("WARN: lost connection to peer", p.NetAddress) 92 break 93 } 94 95 // it is the handler's responsibility to close the connection 96 go g.threadedHandleConn(conn) 97 } 98 g.Disconnect(p.NetAddress) 99 } 100 101 // threadedHandleConn reads header data from a connection, then routes it to the 102 // appropriate handler for further processing. 103 func (g *Gateway) threadedHandleConn(conn modules.PeerConn) { 104 defer conn.Close() 105 var id rpcID 106 if err := encoding.ReadObject(conn, &id, 8); err != nil { 107 return 108 } 109 // call registered handler for this ID 110 lockid := g.mu.RLock() 111 fn, ok := g.handlers[id] 112 g.mu.RUnlock(lockid) 113 if !ok { 114 g.log.Printf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RemoteAddr(), id) 115 return 116 } 117 if build.DEBUG { 118 g.log.Printf("INFO: incoming conn %v requested RPC \"%v\"", conn.RemoteAddr(), id) 119 } 120 121 // call fn 122 err := fn(conn) 123 // don't log benign errors 124 if err == modules.ErrDuplicateTransactionSet || err == modules.ErrBlockKnown { 125 err = nil 126 } 127 if err != nil { 128 g.log.Printf("WARN: incoming RPC \"%v\" from conn %v failed: %v", id, conn.RemoteAddr(), err) 129 } 130 } 131 132 // Broadcast calls an RPC on all of the specified peers. The calls are run in 133 // parallel. Broadcasts are restricted to "one-way" RPCs, which simply write an 134 // object and disconnect. This is why Broadcast takes an interface{} instead of 135 // an RPCFunc. 136 func (g *Gateway) Broadcast(name string, obj interface{}, peers []modules.Peer) { 137 g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers)) 138 139 // only encode obj once, instead of using WriteObject 140 enc := encoding.Marshal(obj) 141 fn := func(conn modules.PeerConn) error { 142 return encoding.WritePrefix(conn, enc) 143 } 144 145 var wg sync.WaitGroup 146 wg.Add(len(peers)) 147 for _, p := range peers { 148 go func(addr modules.NetAddress) { 149 err := g.RPC(addr, name, fn) 150 if err != nil { 151 // try one more time before giving up 152 time.Sleep(10 * time.Second) 153 g.RPC(addr, name, fn) 154 } 155 wg.Done() 156 }(p.NetAddress) 157 } 158 wg.Wait() 159 }