github.com/avahowell/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  }