package main import ( "context" "fmt" "log" "time" "github.com/go-redis/redis/v8" ) type ProjectBackfeedManager struct { Context context.Context Cancel context.CancelFunc Done chan bool C chan *BackfeedItem Name string BackfeedRedis *redis.ClusterClient ProjectRedis *redis.Client //Lock sync.RWMutex ProjectConfig ProjectConfig } type ProjectRedisConfig struct { Host string `json:"host"` Pass string `json:"pass"` Port int `json:"port"` } func (that *ProjectBackfeedManager) RedisConfigDiffers(new *ProjectRedisConfig) bool { if that.ProjectConfig.RedisConfig == nil && new == nil { return false } return that.ProjectConfig.RedisConfig == nil || new == nil || *that.ProjectConfig.RedisConfig != *new } func (that *ProjectBackfeedManager) PushItem(ctx context.Context, item *BackfeedItem) error { //that.Lock.RLock() //defer that.Lock.RUnlock() //if that.C == nil { // return false //} select { case <-ctx.Done(): return ctx.Err() case <-that.Context.Done(): return fmt.Errorf("backfeed channel closed") case that.C <- item: return nil //default: // return fmt.Errorf("backfeed channel full") } } func (that *ProjectBackfeedManager) PopItem(blocking bool) (*BackfeedItem, bool) { if blocking { select { case <-that.Context.Done(): return nil, false case item, ok := <-that.C: return item, ok } } else { select { case <-that.Context.Done(): return nil, false case item, ok := <-that.C: return item, ok default: return nil, false } } } func (that *ProjectBackfeedManager) Do() { defer close(that.Done) //defer that.CloseItemChannel() defer that.Cancel() pipe := that.BackfeedRedis.Pipeline() for { select { case <-that.Context.Done(): break case <-that.Done: break default: } item, ok := that.PopItem(true) if !ok { break } keyMap := map[string][][]byte{} key := fmt.Sprintf("%s:%02x:%s", that.Name, item.PrimaryShard, item.SecondaryShard) keyMap[key] = append(keyMap[key], item.Item) wrapped := 1 for wrapped < ItemWrapSize { item, ok := that.PopItem(false) if !ok { break } key := fmt.Sprintf("%s:%02x:%s", that.Name, item.PrimaryShard, item.SecondaryShard) keyMap[key] = append(keyMap[key], item.Item) wrapped++ } select { case <-that.Context.Done(): break case <-that.Done: break default: } now := time.Now() resultMap := map[string]*redis.Cmd{} lastTS := make([]any, 0, len(keyMap)*2) for key := range keyMap { lastTS = append(lastTS, key) lastTS = append(lastTS, fmt.Sprintf("%d", now.Unix())) } pipe.HSet(context.Background(), ":last_ts", lastTS...) for key, items := range keyMap { args := []any{ "bf.madd", key, } for _, item := range items { args = append(args, item) } resultMap[key] = pipe.Do(context.Background(), args...) } if _, err := pipe.Exec(context.Background()); err != nil { log.Printf("%s", err) } var sAddItems []any for key, items := range keyMap { res, err := resultMap[key].BoolSlice() if err != nil { log.Printf("%s", err) continue } if len(res) != len(keyMap[key]) { continue } for i, v := range res { if v { sAddItems = append(sAddItems, items[i]) } } } dupes := wrapped - len(sAddItems) if len(sAddItems) != 0 { if err := that.ProjectRedis.SAdd(context.Background(), fmt.Sprintf("%s:todo:backfeed", that.Name), sAddItems...).Err(); err != nil { log.Printf("failed to sadd items for %s: %s", that.Name, err) } } if dupes > 0 { that.BackfeedRedis.HIncrBy(context.Background(), ":", that.Name, int64(dupes)) } } }