github.com/nevalang/neva@v0.23.1-0.20240507185603-7696a9bb8dda/internal/runtime/connector.go (about)

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  )
     7  
     8  type Connector struct {
     9  	listener EventListener
    10  }
    11  
    12  func (c Connector) Connect(ctx context.Context, conns []Connection) {
    13  	wg := sync.WaitGroup{}
    14  	wg.Add(len(conns))
    15  
    16  	for i := range conns {
    17  		conn := conns[i]
    18  		go func() {
    19  			c.broadcast(ctx, conn)
    20  			wg.Done()
    21  		}()
    22  	}
    23  
    24  	wg.Wait()
    25  }
    26  
    27  func (c Connector) broadcast(ctx context.Context, conn Connection) {
    28  	receiversForEvent := getReceiversForEvent(conn)
    29  
    30  	for {
    31  		select {
    32  		case <-ctx.Done():
    33  			return
    34  		case msg := <-conn.Sender:
    35  			event := Event{
    36  				Type: MessageSentEvent,
    37  				MessageSent: &EventMessageSent{
    38  					SenderPortAddr:    conn.Meta.SenderPortAddr,
    39  					ReceiverPortAddrs: receiversForEvent,
    40  				},
    41  			}
    42  			// distribute will send to this channel after processing first receiver
    43  			// warning: it's not clear whether it's safe to move on before all receivers processed
    44  			// order of messages must be preserved while distribute goroutines might be concurrent to each other
    45  
    46  			c.distribute(
    47  				ctx,
    48  				c.listener.Send(event, msg),
    49  				conn.Meta,
    50  				conn.Receivers,
    51  			)
    52  		}
    53  	}
    54  }
    55  
    56  func getReceiversForEvent(conn Connection) map[PortAddr]struct{} {
    57  	receiversForEvent := make(map[PortAddr]struct{}, len(conn.Meta.ReceiverPortAddrs))
    58  	for _, receiverPortAddr := range conn.Meta.ReceiverPortAddrs {
    59  		receiversForEvent[receiverPortAddr] = struct{}{}
    60  	}
    61  	return receiversForEvent
    62  }
    63  
    64  // distribute implements the "Queue-based Round-Robin Algorithm".
    65  func (c Connector) distribute(
    66  	ctx context.Context,
    67  	msg Msg,
    68  	meta ConnectionMeta,
    69  	receiverChans []chan Msg,
    70  ) {
    71  	i := 0
    72  	interceptedMsgs := make(map[PortAddr]Msg, len(receiverChans)) // we can handle same receiver multiple times
    73  
    74  	// we make copy because we're gonna modify it
    75  	// this is crucial because this array is shared across goroutines
    76  	queue := make([]chan Msg, len(receiverChans))
    77  	copy(queue, receiverChans)
    78  	receiversPortAddrs := make([]PortAddr, len(receiverChans))
    79  	copy(receiversPortAddrs, meta.ReceiverPortAddrs)
    80  
    81  	for len(queue) > 0 {
    82  		curRecv := queue[i]
    83  		recvPortAddr := receiversPortAddrs[i]
    84  
    85  		if _, ok := interceptedMsgs[recvPortAddr]; !ok { // avoid multuple interceptions
    86  			event := Event{
    87  				Type: MessagePendingEvent,
    88  				MessagePending: &EventMessagePending{
    89  					Meta:             meta,
    90  					ReceiverPortAddr: recvPortAddr,
    91  				},
    92  			}
    93  			msg = c.listener.Send(event, msg)
    94  			interceptedMsgs[recvPortAddr] = msg
    95  		}
    96  		interceptedMsg := interceptedMsgs[recvPortAddr]
    97  
    98  		select {
    99  		case <-ctx.Done():
   100  			return
   101  		case curRecv <- interceptedMsg: // receiver has accepted the message
   102  			event := Event{
   103  				Type: MessageReceivedEvent,
   104  				MessageReceived: &EventMessageReceived{
   105  					Meta:             meta,
   106  					ReceiverPortAddr: recvPortAddr,
   107  				},
   108  			}
   109  
   110  			msg = c.listener.Send(event, msg) // notify listener about the event and save intercepted message
   111  
   112  			// remove current receiver from queue
   113  			queue = append(queue[:i], queue[i+1:]...) // this append modifies array
   114  			receiversPortAddrs = append(receiversPortAddrs[:i], receiversPortAddrs[i+1:]...)
   115  		default: // current receiver is busy
   116  			if i < len(queue) { // so if we are not at the end of the queue
   117  				i++ // then go try next receiver
   118  			}
   119  		}
   120  
   121  		if i == len(queue) { // if this is the end of the queue (and loop isn't over)
   122  			i = 0 // then start over
   123  		}
   124  	}
   125  }
   126  
   127  func NewDefaultConnector() Connector {
   128  	return Connector{
   129  		listener: EmptyListener{},
   130  	}
   131  }
   132  
   133  func NewConnector(lis EventListener) Connector {
   134  	return Connector{
   135  		listener: lis,
   136  	}
   137  }