github.com/gdamore/mangos@v1.4.0/protocol/star/star.go (about)

     1  // Copyright 2018 The Mangos Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package star implements a new, experimental protocol called "STAR".
    16  // This is like the BUS protocol, except that each member of the network
    17  // automatically forwards any message it receives to any other peers.
    18  // In a star network, this means that all members should receive all messages,
    19  // assuming that there is a central server.  Its important to ensure that
    20  // the topology is free from cycles, as there is no protection against
    21  // that, and cycles can lead to infinite message storms.  (TODO: Add a TTL,
    22  // and basic message ID / anti-replay protection.)
    23  package star
    24  
    25  import (
    26  	"sync"
    27  	"time"
    28  
    29  	"nanomsg.org/go-mangos"
    30  )
    31  
    32  type starEp struct {
    33  	ep mangos.Endpoint
    34  	q  chan *mangos.Message
    35  	x  *star
    36  }
    37  
    38  type star struct {
    39  	sock mangos.ProtocolSocket
    40  	eps  map[uint32]*starEp
    41  	raw  bool
    42  	w    mangos.Waiter
    43  	ttl  int
    44  
    45  	sync.Mutex
    46  }
    47  
    48  func (x *star) Init(sock mangos.ProtocolSocket) {
    49  	x.sock = sock
    50  	x.eps = make(map[uint32]*starEp)
    51  	x.ttl = 8
    52  	x.w.Init()
    53  	x.w.Add()
    54  	go x.sender()
    55  }
    56  
    57  func (x *star) Shutdown(expire time.Time) {
    58  
    59  	x.w.WaitAbsTimeout(expire)
    60  
    61  	x.Lock()
    62  	peers := x.eps
    63  	x.eps = make(map[uint32]*starEp)
    64  	x.Unlock()
    65  
    66  	for id, peer := range peers {
    67  		delete(peers, id)
    68  		mangos.DrainChannel(peer.q, expire)
    69  		close(peer.q)
    70  	}
    71  }
    72  
    73  // Bottom sender.
    74  func (pe *starEp) sender() {
    75  	for {
    76  		m := <-pe.q
    77  		if m == nil {
    78  			break
    79  		}
    80  
    81  		if pe.ep.SendMsg(m) != nil {
    82  			m.Free()
    83  			break
    84  		}
    85  	}
    86  }
    87  
    88  func (x *star) broadcast(m *mangos.Message, sender *starEp) {
    89  
    90  	x.Lock()
    91  	if sender == nil || !x.raw {
    92  		for _, pe := range x.eps {
    93  			if sender == pe {
    94  				continue
    95  			}
    96  			m = m.Dup()
    97  			select {
    98  			case pe.q <- m:
    99  			default:
   100  				// No room on outbound queue, drop it.
   101  				if m != nil {
   102  					m.Free()
   103  				}
   104  			}
   105  		}
   106  	}
   107  	x.Unlock()
   108  
   109  	// Grab a local copy and send it up if we aren't originator
   110  	if sender != nil {
   111  		select {
   112  		case x.sock.RecvChannel() <- m:
   113  		case <-x.sock.CloseChannel():
   114  			m.Free()
   115  			return
   116  		default:
   117  			// No room, so we just drop it.
   118  			m.Free()
   119  		}
   120  	} else {
   121  		// Not sending it up, so we need to release it.
   122  		m.Free()
   123  	}
   124  }
   125  
   126  func (x *star) sender() {
   127  	defer x.w.Done()
   128  	cq := x.sock.CloseChannel()
   129  	sq := x.sock.SendChannel()
   130  
   131  	for {
   132  		select {
   133  		case <-cq:
   134  			return
   135  		case m := <-sq:
   136  			if m == nil {
   137  				sq = x.sock.SendChannel()
   138  				continue
   139  			}
   140  			x.broadcast(m, nil)
   141  		}
   142  	}
   143  }
   144  
   145  func (pe *starEp) receiver() {
   146  	for {
   147  		m := pe.ep.RecvMsg()
   148  		if m == nil {
   149  			return
   150  		}
   151  
   152  		if len(m.Body) < 4 {
   153  			m.Free()
   154  			continue
   155  		}
   156  		if m.Body[0] != 0 || m.Body[1] != 0 || m.Body[2] != 0 {
   157  			// non-zero reserved fields are illegal
   158  			m.Free()
   159  			continue
   160  		}
   161  		if int(m.Body[3]) >= pe.x.ttl { // TTL expired?
   162  			// XXX: bump a stat
   163  			m.Free()
   164  			continue
   165  		}
   166  		m.Header = append(m.Header, 0, 0, 0, m.Body[3]+1)
   167  		m.Body = m.Body[4:]
   168  
   169  		// if we're in raw mode, this does only a sendup, otherwise
   170  		// it does both a retransmit + sendup
   171  		pe.x.broadcast(m, pe)
   172  	}
   173  }
   174  
   175  func (x *star) AddEndpoint(ep mangos.Endpoint) {
   176  	depth := 16
   177  	if i, err := x.sock.GetOption(mangos.OptionWriteQLen); err == nil {
   178  		depth = i.(int)
   179  	}
   180  	pe := &starEp{ep: ep, x: x, q: make(chan *mangos.Message, depth)}
   181  	x.Lock()
   182  	x.eps[ep.GetID()] = pe
   183  	x.Unlock()
   184  	go pe.sender()
   185  	go pe.receiver()
   186  }
   187  
   188  func (x *star) RemoveEndpoint(ep mangos.Endpoint) {
   189  	x.Lock()
   190  	if peer := x.eps[ep.GetID()]; peer != nil {
   191  		delete(x.eps, ep.GetID())
   192  		close(peer.q)
   193  	}
   194  	x.Unlock()
   195  }
   196  
   197  func (*star) Number() uint16 {
   198  	return mangos.ProtoStar
   199  }
   200  
   201  func (*star) PeerNumber() uint16 {
   202  	return mangos.ProtoStar
   203  }
   204  
   205  func (*star) Name() string {
   206  	return "star"
   207  }
   208  
   209  func (*star) PeerName() string {
   210  	return "star"
   211  }
   212  
   213  func (x *star) SetOption(name string, v interface{}) error {
   214  	var ok bool
   215  	switch name {
   216  	case mangos.OptionRaw:
   217  		if x.raw, ok = v.(bool); !ok {
   218  			return mangos.ErrBadValue
   219  		}
   220  		return nil
   221  	case mangos.OptionTTL:
   222  		if ttl, ok := v.(int); !ok {
   223  			return mangos.ErrBadValue
   224  		} else if ttl < 1 || ttl > 255 {
   225  			return mangos.ErrBadValue
   226  		} else {
   227  			x.ttl = ttl
   228  		}
   229  		return nil
   230  	default:
   231  		return mangos.ErrBadOption
   232  	}
   233  }
   234  
   235  func (x *star) GetOption(name string) (interface{}, error) {
   236  	switch name {
   237  	case mangos.OptionRaw:
   238  		return x.raw, nil
   239  	case mangos.OptionTTL:
   240  		return x.ttl, nil
   241  	default:
   242  		return nil, mangos.ErrBadOption
   243  	}
   244  }
   245  
   246  func (x *star) SendHook(m *mangos.Message) bool {
   247  
   248  	if x.raw {
   249  		// TTL header must be present.
   250  		return true
   251  	}
   252  	// new message has a zero hop count
   253  	m.Header = append(m.Header, 0, 0, 0, 0)
   254  	return true
   255  }
   256  
   257  // NewSocket allocates a new Socket using the STAR protocol.
   258  func NewSocket() (mangos.Socket, error) {
   259  	return mangos.MakeSocket(&star{}), nil
   260  }