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