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 }