66package feed
77
88import (
9- "context"
109 "sync"
1110)
1211
12+ // Feed defines a set of subscriptions per topic T which receive messages sent
13+ // to the Feed.
1314type Feed [T comparable , M any ] struct {
14- channels map [T ][]chan M
15- mu sync.RWMutex
15+ subscriptions map [T ][]* subscription [ M ]
16+ mu sync.RWMutex
1617
1718 wg sync.WaitGroup
1819 quit chan struct {}
1920 quitOnce sync.Once
2021}
2122
23+ // NewFeed constructs new Feed with topic type T and message type M.
2224func NewFeed [T comparable , M any ]() * Feed [T , M ] {
2325 return & Feed [T , M ]{
24- channels : make (map [T ][]chan M , 1 ),
25- quit : make (chan struct {}),
26+ subscriptions : make (map [T ][]* subscription [ M ] ),
27+ quit : make (chan struct {}),
2628 }
2729}
2830
31+ // Subscribe returns a channel from which messages M, that are sent to the Feed
32+ // on the same topic, can be read from. Message delivery is guaranteed, so the
33+ // channel should be read to avoid possible high number of goroutines. After
34+ // cancel function call, all resources ang goroutines are released even if not
35+ // all messages are read from channel.
2936func (f * Feed [T , M ]) Subscribe (topic T ) (c <- chan M , cancel func ()) {
30- channel := make (chan M )
37+ channel := make (chan M , 1 ) // buffer 1 not to block on Send method
3138
3239 select {
3340 case <- f .quit :
@@ -39,24 +46,27 @@ func (f *Feed[T, M]) Subscribe(topic T) (c <-chan M, cancel func()) {
3946 f .mu .Lock ()
4047 defer f .mu .Unlock ()
4148
42- f . channels [ topic ] = append ( f . channels [ topic ], channel )
49+ s := newSubscription ( channel )
4350
44- return channel , func () { f .unsubscribe (topic , channel ) }
51+ f .subscriptions [topic ] = append (f .subscriptions [topic ], s )
52+
53+ return channel , func () { f .unsubscribe (topic , s ) }
4554}
4655
47- func (f * Feed [T , M ]) unsubscribe (topic T , c <- chan M ) {
56+ func (f * Feed [T , M ]) unsubscribe (topic T , s * subscription [ M ] ) {
4857 f .mu .Lock ()
4958 defer f .mu .Unlock ()
5059
51- for i , ch := range f .channels [topic ] {
52- if ch == c {
53- f .channels [topic ] = append (f .channels [topic ][:i ], f .channels [topic ][i + 1 :]... )
54- close (ch )
60+ for i , sub := range f .subscriptions [topic ] {
61+ if sub == s {
62+ f .subscriptions [topic ] = append (f .subscriptions [topic ][:i ], f .subscriptions [topic ][i + 1 :]... )
63+ s . close ()
5564 }
5665 }
5766}
5867
59- func (f * Feed [T , M ]) Shutdown (ctx context.Context ) error {
68+ // Close terminates all subscriptions and releases acquired resources.
69+ func (f * Feed [T , M ]) Close () error {
6070 f .quitOnce .Do (func () {
6171 close (f .quit )
6272 })
@@ -66,46 +76,48 @@ func (f *Feed[T, M]) Shutdown(ctx context.Context) error {
6676 close (done )
6777 }()
6878
69- select {
70- case <- ctx .Done ():
71- return ctx .Err ()
72- case <- done :
73- }
74-
7579 f .mu .Lock ()
7680 defer f .mu .Unlock ()
7781
78- for topic , channels := range f .channels {
79- for _ , c := range channels {
80- close (c )
82+ for topic , subscriptions := range f .subscriptions {
83+ for _ , s := range subscriptions {
84+ s . close ()
8185 }
82- f .channels [topic ] = nil
86+ f .subscriptions [topic ] = nil
8387 }
8488
8589 return nil
8690}
8791
92+ // Send sends a message to all sunscribed channels to topic. Messages will be
93+ // delivered to subscribers when each of them is ready to receive it, without
94+ // blocking this method call. The returned integer is the number of subscribers
95+ // that should receive the message.
8896func (f * Feed [T , M ]) Send (topic T , message M ) (n int ) {
8997 f .mu .RLock ()
9098 defer f .mu .RUnlock ()
9199
92- for _ , c := range f .channels [topic ] {
100+ for _ , s := range f .subscriptions [topic ] {
93101 // try to send message to the channel
94102 select {
95- case c <- message :
103+ case s .channel <- message :
104+ case <- s .quit :
105+ return
96106 case <- f .quit :
97107 return
98108 default :
99109 // if channel is blocked,
100110 // wait in goroutine to send the message
101- c := c
111+ s := s
102112
103113 f .wg .Add (1 )
104114 go func () {
105115 defer f .wg .Done ()
106116
107117 select {
108- case c <- message :
118+ case s .channel <- message :
119+ case <- s .quit :
120+ return
109121 case <- f .quit :
110122 return
111123 }
@@ -117,3 +129,24 @@ func (f *Feed[T, M]) Send(topic T, message M) (n int) {
117129
118130 return n
119131}
132+
133+ type subscription [M any ] struct {
134+ channel chan M
135+ quitOnce sync.Once
136+ quit chan struct {}
137+ wg sync.WaitGroup
138+ }
139+
140+ func newSubscription [M any ](channel chan M ) * subscription [M ] {
141+ return & subscription [M ]{
142+ channel : channel ,
143+ quit : make (chan struct {}),
144+ }
145+ }
146+
147+ func (s * subscription [M ]) close () {
148+ s .quitOnce .Do (func () {
149+ close (s .quit )
150+ })
151+ s .wg .Wait ()
152+ }
0 commit comments