github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/events/stream/memory/memory.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/events/stream/memory/memory.go 14 15 package memory 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "sync" 21 "time" 22 23 "github.com/google/uuid" 24 "github.com/tickoalcantara12/micro/v3/service/events" 25 "github.com/tickoalcantara12/micro/v3/service/logger" 26 "github.com/tickoalcantara12/micro/v3/service/store" 27 "github.com/tickoalcantara12/micro/v3/service/store/memory" 28 "github.com/pkg/errors" 29 ) 30 31 // NewStream returns an initialized memory stream 32 func NewStream(opts ...Option) (events.Stream, error) { 33 // parse the options 34 var options Options 35 for _, o := range opts { 36 o(&options) 37 } 38 if options.Store == nil { 39 options.Store = memory.NewStore() 40 } 41 42 return &mem{store: options.Store}, nil 43 } 44 45 type subscriber struct { 46 Group string 47 Topic string 48 Channel chan events.Event 49 50 sync.RWMutex 51 retryMap map[string]int 52 retryLimit int 53 autoAck bool 54 ackWait time.Duration 55 } 56 57 type mem struct { 58 store store.Store 59 60 subs []*subscriber 61 sync.RWMutex 62 } 63 64 func (m *mem) Publish(topic string, msg interface{}, opts ...events.PublishOption) error { 65 // validate the topic 66 if len(topic) == 0 { 67 return events.ErrMissingTopic 68 } 69 70 // parse the options 71 options := events.PublishOptions{ 72 Timestamp: time.Now(), 73 } 74 for _, o := range opts { 75 o(&options) 76 } 77 78 // encode the message if it's not already encoded 79 var payload []byte 80 if p, ok := msg.([]byte); ok { 81 payload = p 82 } else { 83 p, err := json.Marshal(msg) 84 if err != nil { 85 return events.ErrEncodingMessage 86 } 87 payload = p 88 } 89 90 // construct the event 91 event := &events.Event{ 92 ID: uuid.New().String(), 93 Topic: topic, 94 Timestamp: options.Timestamp, 95 Metadata: options.Metadata, 96 Payload: payload, 97 } 98 99 // serialize the event to bytes 100 bytes, err := json.Marshal(event) 101 if err != nil { 102 return errors.Wrap(err, "Error encoding event") 103 } 104 105 // write to the store 106 key := fmt.Sprintf("%v/%v", event.Topic, event.ID) 107 if err := m.store.Write(&store.Record{Key: key, Value: bytes}); err != nil { 108 return errors.Wrap(err, "Error writing event to store") 109 } 110 111 // send to the subscribers async 112 go m.handleEvent(event) 113 114 return nil 115 } 116 117 func (m *mem) Consume(topic string, opts ...events.ConsumeOption) (<-chan events.Event, error) { 118 // validate the topic 119 if len(topic) == 0 { 120 return nil, events.ErrMissingTopic 121 } 122 123 // parse the options 124 options := events.ConsumeOptions{ 125 Group: uuid.New().String(), 126 AutoAck: true, 127 } 128 for _, o := range opts { 129 o(&options) 130 } 131 // TODO RetryLimit 132 133 // setup the subscriber 134 sub := &subscriber{ 135 Channel: make(chan events.Event), 136 Topic: topic, 137 Group: options.Group, 138 retryMap: map[string]int{}, 139 autoAck: true, 140 retryLimit: options.GetRetryLimit(), 141 } 142 143 if !options.AutoAck { 144 if options.AckWait == 0 { 145 return nil, fmt.Errorf("invalid AckWait passed, should be positive integer") 146 } 147 sub.autoAck = options.AutoAck 148 sub.ackWait = options.AckWait 149 } 150 151 // register the subscriber 152 m.Lock() 153 m.subs = append(m.subs, sub) 154 m.Unlock() 155 156 // lookup previous events if the start time option was passed 157 if options.Offset.Unix() > 0 { 158 go m.lookupPreviousEvents(sub, options.Offset) 159 } 160 161 // return the channel 162 return sub.Channel, nil 163 } 164 165 // lookupPreviousEvents finds events for a subscriber which occurred before a given time and sends 166 // them into the subscribers channel 167 func (m *mem) lookupPreviousEvents(sub *subscriber, startTime time.Time) { 168 // lookup all events which match the topic (a blank topic will return all results) 169 recs, err := m.store.Read(sub.Topic+"/", store.ReadPrefix()) 170 if err != nil && logger.V(logger.ErrorLevel, logger.DefaultLogger) { 171 logger.Errorf("Error looking up previous events: %v", err) 172 return 173 } else if err != nil { 174 return 175 } 176 177 // loop through the records and send it to the channel if it matches 178 for _, r := range recs { 179 var ev events.Event 180 if err := json.Unmarshal(r.Value, &ev); err != nil { 181 continue 182 } 183 if ev.Timestamp.Unix() < startTime.Unix() { 184 continue 185 } 186 sendEvent(&ev, sub) 187 } 188 } 189 190 // handleEvents sends the event to any registered subscribers. 191 func (m *mem) handleEvent(ev *events.Event) { 192 m.RLock() 193 subs := m.subs 194 m.RUnlock() 195 196 // filteredSubs is a KV map of the queue name and subscribers. This is used to prevent a message 197 // being sent to two subscribers with the same queue. 198 filteredSubs := map[string]*subscriber{} 199 200 // filter down to subscribers who are interested in this topic 201 for _, sub := range subs { 202 if len(sub.Topic) == 0 || sub.Topic == ev.Topic { 203 filteredSubs[sub.Group] = sub 204 } 205 } 206 207 // send the message to each channel async (since one channel might be blocked) 208 for _, sub := range filteredSubs { 209 sendEvent(ev, sub) 210 } 211 } 212 213 func sendEvent(ev *events.Event, sub *subscriber) { 214 go func(s *subscriber) { 215 evCopy := *ev 216 if s.autoAck { 217 s.Channel <- evCopy 218 return 219 } 220 evCopy.SetAckFunc(ackFunc(s, evCopy)) 221 evCopy.SetNackFunc(nackFunc(s, evCopy)) 222 s.retryMap[evCopy.ID] = 0 223 tick := time.NewTicker(s.ackWait) 224 defer tick.Stop() 225 for range tick.C { 226 s.Lock() 227 count, ok := s.retryMap[evCopy.ID] 228 s.Unlock() 229 if !ok { 230 // success 231 break 232 } 233 234 if s.retryLimit > -1 && count > s.retryLimit { 235 if logger.V(logger.ErrorLevel, logger.DefaultLogger) { 236 logger.Errorf("Message retry limit reached, discarding: %v %d %d", evCopy.ID, count, s.retryLimit) 237 } 238 s.Lock() 239 delete(s.retryMap, evCopy.ID) 240 s.Unlock() 241 return 242 } 243 s.Channel <- evCopy 244 s.Lock() 245 s.retryMap[evCopy.ID] = count + 1 246 s.Unlock() 247 } 248 }(sub) 249 } 250 251 func ackFunc(s *subscriber, evCopy events.Event) func() error { 252 return func() error { 253 s.Lock() 254 delete(s.retryMap, evCopy.ID) 255 s.Unlock() 256 return nil 257 } 258 } 259 260 func nackFunc(s *subscriber, evCopy events.Event) func() error { 261 return func() error { 262 return nil 263 } 264 }