github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/tracker_loader.go (about) 1 package service 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/keybase/client/go/engine" 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/keybase1" 12 context "golang.org/x/net/context" 13 "golang.org/x/sync/errgroup" 14 ) 15 16 type TrackerLoader struct { 17 libkb.Contextified 18 sync.Mutex 19 20 eg errgroup.Group 21 started bool 22 shutdownCh chan struct{} 23 queueCh chan keybase1.UID 24 25 cancel context.CancelFunc 26 } 27 28 func NewTrackerLoader(g *libkb.GlobalContext) *TrackerLoader { 29 l := &TrackerLoader{ 30 Contextified: libkb.NewContextified(g), 31 shutdownCh: make(chan struct{}), 32 queueCh: make(chan keybase1.UID, 100), 33 } 34 35 g.PushShutdownHook(func(mctx libkb.MetaContext) error { 36 <-l.Shutdown(mctx.Ctx()) 37 return nil 38 }) 39 return l 40 } 41 42 func (l *TrackerLoader) debug(ctx context.Context, msg string, args ...interface{}) { 43 l.G().Log.CDebugf(ctx, "TrackerLoader: %s", fmt.Sprintf(msg, args...)) 44 } 45 46 func (l *TrackerLoader) Run(ctx context.Context) { 47 defer l.G().CTrace(ctx, "TrackerLoader.Run", nil)() 48 l.Lock() 49 defer l.Unlock() 50 if l.started { 51 return 52 } 53 l.started = true 54 l.shutdownCh = make(chan struct{}) 55 lctx, lcancel := context.WithCancel(ctx) 56 l.cancel = lcancel 57 l.eg.Go(func() error { return l.loadLoop(lctx, l.shutdownCh) }) 58 } 59 60 func (l *TrackerLoader) Shutdown(ctx context.Context) chan struct{} { 61 defer l.G().CTrace(ctx, "TrackerLoader.Shutdown", nil)() 62 l.Lock() 63 defer l.Unlock() 64 ch := make(chan struct{}) 65 if l.started { 66 l.cancel() 67 close(l.shutdownCh) 68 l.started = false 69 go func() { 70 _ = l.eg.Wait() 71 close(ch) 72 }() 73 } else { 74 close(ch) 75 } 76 return ch 77 } 78 79 func (l *TrackerLoader) Queue(ctx context.Context, uid keybase1.UID) (err error) { 80 defer l.G().CTrace(ctx, fmt.Sprintf("TrackerLoader.Queue: uid: %s", uid), &err)() 81 select { 82 case l.queueCh <- uid: 83 default: 84 return errors.New("queue full") 85 } 86 return nil 87 } 88 89 var cachedOnlyStalenessWindow = time.Hour * 24 * 7 90 91 func (l *TrackerLoader) trackingArg(mctx libkb.MetaContext, uid keybase1.UID, withNetwork bool) *engine.ListTrackingEngineArg { 92 arg := &engine.ListTrackingEngineArg{UID: uid, CachedOnly: !withNetwork} 93 if !withNetwork { 94 arg.CachedOnlyStalenessWindow = &cachedOnlyStalenessWindow 95 } 96 return arg 97 } 98 99 func (l *TrackerLoader) trackersArg(uid keybase1.UID, withNetwork bool) engine.ListTrackersUnverifiedEngineArg { 100 return engine.ListTrackersUnverifiedEngineArg{UID: uid, CachedOnly: !withNetwork} 101 } 102 103 func (l *TrackerLoader) loadInner(mctx libkb.MetaContext, uid keybase1.UID, withNetwork bool) error { 104 eng := engine.NewListTrackingEngine(mctx.G(), l.trackingArg(mctx, uid, withNetwork)) 105 err := engine.RunEngine2(mctx, eng) 106 if err != nil { 107 return err 108 } 109 following := eng.TableResult() 110 111 eng2 := engine.NewListTrackersUnverifiedEngine(mctx.G(), l.trackersArg(uid, withNetwork)) 112 err = engine.RunEngine2(mctx, eng2) 113 if err != nil { 114 return err 115 } 116 followers := eng2.GetResults() 117 118 l.debug(mctx.Ctx(), "loadInner: loaded %d followers, %d following", len(followers.Users), len(following.Users)) 119 l.G().NotifyRouter.HandleTrackingInfo(keybase1.TrackingInfoArg{ 120 Uid: uid, 121 Followees: following.Usernames(), 122 Followers: followers.Usernames(), 123 }) 124 125 return nil 126 } 127 128 func (l *TrackerLoader) load(ctx context.Context, uid keybase1.UID) error { 129 defer l.G().CTrace(ctx, "TrackerLoader.load", nil)() 130 131 mctx := libkb.NewMetaContext(ctx, l.G()) 132 133 withNetwork := false 134 if err := l.loadInner(mctx, uid, withNetwork); err != nil { 135 l.debug(ctx, "load: failed to load from local storage: %s", err) 136 } 137 138 withNetwork = true 139 return l.loadInner(mctx, uid, withNetwork) 140 } 141 142 func (l *TrackerLoader) loadLoop(ctx context.Context, stopCh chan struct{}) error { 143 for { 144 select { 145 case uid := <-l.queueCh: 146 err := l.load(ctx, uid) 147 if err != nil { 148 l.debug(ctx, "loadLoop: failed to load: %s", err) 149 } else { 150 l.debug(ctx, "loadLoop: load tracks for %s successfully", uid) 151 } 152 case <-stopCh: 153 return nil 154 } 155 } 156 }