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  }