github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/rpc/mux_broker.go (about)

     1  package rpc
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/hashicorp/yamux"
    12  )
    13  
    14  // muxBroker is responsible for brokering multiplexed connections by unique ID.
    15  //
    16  // This allows a plugin to request a channel with a specific ID to connect to
    17  // or accept a connection from, and the broker handles the details of
    18  // holding these channels open while they're being negotiated.
    19  type muxBroker struct {
    20  	nextId  uint32
    21  	session *yamux.Session
    22  	streams map[uint32]*muxBrokerPending
    23  
    24  	sync.Mutex
    25  }
    26  
    27  type muxBrokerPending struct {
    28  	ch     chan net.Conn
    29  	doneCh chan struct{}
    30  }
    31  
    32  func newMuxBroker(s *yamux.Session) *muxBroker {
    33  	return &muxBroker{
    34  		session: s,
    35  		streams: make(map[uint32]*muxBrokerPending),
    36  	}
    37  }
    38  
    39  // Accept accepts a connection by ID.
    40  //
    41  // This should not be called multiple times with the same ID at one time.
    42  func (m *muxBroker) Accept(id uint32) (net.Conn, error) {
    43  	var c net.Conn
    44  	p := m.getStream(id)
    45  	select {
    46  	case c = <-p.ch:
    47  		close(p.doneCh)
    48  	case <-time.After(5 * time.Second):
    49  		m.Lock()
    50  		defer m.Unlock()
    51  		delete(m.streams, id)
    52  
    53  		return nil, fmt.Errorf("timeout waiting for accept")
    54  	}
    55  
    56  	// Ack our connection
    57  	if err := binary.Write(c, binary.LittleEndian, id); err != nil {
    58  		c.Close()
    59  		return nil, err
    60  	}
    61  
    62  	return c, nil
    63  }
    64  
    65  // Close closes the connection and all sub-connections.
    66  func (m *muxBroker) Close() error {
    67  	return m.session.Close()
    68  }
    69  
    70  // Dial opens a connection by ID.
    71  func (m *muxBroker) Dial(id uint32) (net.Conn, error) {
    72  	// Open the stream
    73  	stream, err := m.session.OpenStream()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// Write the stream ID onto the wire.
    79  	if err := binary.Write(stream, binary.LittleEndian, id); err != nil {
    80  		stream.Close()
    81  		return nil, err
    82  	}
    83  
    84  	// Read the ack that we connected. Then we're off!
    85  	var ack uint32
    86  	if err := binary.Read(stream, binary.LittleEndian, &ack); err != nil {
    87  		stream.Close()
    88  		return nil, err
    89  	}
    90  	if ack != id {
    91  		stream.Close()
    92  		return nil, fmt.Errorf("bad ack: %d (expected %d)", ack, id)
    93  	}
    94  
    95  	return stream, nil
    96  }
    97  
    98  // NextId returns a unique ID to use next.
    99  func (m *muxBroker) NextId() uint32 {
   100  	return atomic.AddUint32(&m.nextId, 1)
   101  }
   102  
   103  // Run starts the brokering and should be executed in a goroutine, since it
   104  // blocks forever, or until the session closes.
   105  func (m *muxBroker) Run() {
   106  	for {
   107  		stream, err := m.session.AcceptStream()
   108  		if err != nil {
   109  			// Once we receive an error, just exit
   110  			break
   111  		}
   112  
   113  		// Read the stream ID from the stream
   114  		var id uint32
   115  		if err := binary.Read(stream, binary.LittleEndian, &id); err != nil {
   116  			stream.Close()
   117  			continue
   118  		}
   119  
   120  		// Initialize the waiter
   121  		p := m.getStream(id)
   122  		select {
   123  		case p.ch <- stream:
   124  		default:
   125  		}
   126  
   127  		// Wait for a timeout
   128  		go m.timeoutWait(id, p)
   129  	}
   130  }
   131  
   132  func (m *muxBroker) getStream(id uint32) *muxBrokerPending {
   133  	m.Lock()
   134  	defer m.Unlock()
   135  
   136  	p, ok := m.streams[id]
   137  	if ok {
   138  		return p
   139  	}
   140  
   141  	m.streams[id] = &muxBrokerPending{
   142  		ch:     make(chan net.Conn, 1),
   143  		doneCh: make(chan struct{}),
   144  	}
   145  	return m.streams[id]
   146  }
   147  
   148  func (m *muxBroker) timeoutWait(id uint32, p *muxBrokerPending) {
   149  	// Wait for the stream to either be picked up and connected, or
   150  	// for a timeout.
   151  	timeout := false
   152  	select {
   153  	case <-p.doneCh:
   154  	case <-time.After(5 * time.Second):
   155  		timeout = true
   156  	}
   157  
   158  	m.Lock()
   159  	defer m.Unlock()
   160  
   161  	// Delete the stream so no one else can grab it
   162  	delete(m.streams, id)
   163  
   164  	// If we timed out, then check if we have a channel in the buffer,
   165  	// and if so, close it.
   166  	if timeout {
   167  		select {
   168  		case s := <-p.ch:
   169  			s.Close()
   170  		}
   171  	}
   172  }