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  }