github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/upak_loader.go (about)

     1  package libkb
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"sync"
     9  
    10  	lru "github.com/hashicorp/golang-lru"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"golang.org/x/net/context"
    13  	"golang.org/x/sync/errgroup"
    14  )
    15  
    16  // UPAK Loader is a loader for UserPlusKeysV2AllIncarnations. It's a thin user object that is
    17  // almost as good for many purposes, but can be safely copied and serialized.
    18  type UPAKLoader interface {
    19  	LoginAs(u keybase1.UID) (err error)
    20  	OnLogout() (err error)
    21  	ClearMemory()
    22  	Load(arg LoadUserArg) (ret *keybase1.UserPlusAllKeys, user *User, err error)
    23  	LoadV2(arg LoadUserArg) (ret *keybase1.UserPlusKeysV2AllIncarnations, user *User, err error)
    24  	LoadLite(arg LoadUserArg) (ret *keybase1.UPKLiteV1AllIncarnations, err error)
    25  	CheckKIDForUID(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (found bool, revokedAt *keybase1.KeybaseTime, deleted bool, err error)
    26  	LoadUserPlusKeys(ctx context.Context, uid keybase1.UID, pollForKID keybase1.KID) (keybase1.UserPlusKeys, error)
    27  	LoadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (*keybase1.UserPlusKeysV2, *keybase1.UserPlusKeysV2AllIncarnations, *keybase1.PublicKeyV2NaCl, error)
    28  	Invalidate(ctx context.Context, uid keybase1.UID)
    29  	LoadDeviceKey(ctx context.Context, uid keybase1.UID, deviceID keybase1.DeviceID) (upak *keybase1.UserPlusAllKeys, deviceKey *keybase1.PublicKey, revoked *keybase1.RevokedKey, err error)
    30  	LoadUPAKWithDeviceID(ctx context.Context, uid keybase1.UID, deviceID keybase1.DeviceID) (*keybase1.UserPlusKeysV2AllIncarnations, error)
    31  	LookupUsername(ctx context.Context, uid keybase1.UID) (NormalizedUsername, error)
    32  	LookupUsernameUPAK(ctx context.Context, uid keybase1.UID) (NormalizedUsername, error)
    33  	LookupUID(ctx context.Context, un NormalizedUsername) (keybase1.UID, error)
    34  	LookupUsernameAndDevice(ctx context.Context, uid keybase1.UID, did keybase1.DeviceID) (username NormalizedUsername, deviceName string, deviceType keybase1.DeviceTypeV2, err error)
    35  	PutUserToCache(ctx context.Context, user *User) error
    36  	LoadV2WithKID(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (*keybase1.UserPlusKeysV2AllIncarnations, error)
    37  	CheckDeviceForUIDAndUsername(ctx context.Context, uid keybase1.UID, did keybase1.DeviceID, n NormalizedUsername, suppressNetworkErrors bool) error
    38  	Batcher(ctx context.Context, getArg func(int) *LoadUserArg, processResult func(int, *keybase1.UserPlusKeysV2AllIncarnations) error, window int) (err error)
    39  }
    40  
    41  // CachedUPAKLoader is a UPAKLoader implementation that can cache results both
    42  // in memory and on disk.
    43  type CachedUPAKLoader struct {
    44  	Contextified
    45  	sync.Mutex
    46  	cache          *lru.Cache
    47  	locktab        *LockTable
    48  	Freshness      time.Duration
    49  	noCache        bool
    50  	TestDeadlocker func()
    51  	currentUID     keybase1.UID
    52  }
    53  
    54  // NewCachedUPAKLoader constructs a new CachedUPAKLoader
    55  func NewCachedUPAKLoader(g *GlobalContext, f time.Duration) *CachedUPAKLoader {
    56  	c, err := lru.New(g.Env.GetUPAKCacheSize())
    57  	if err != nil {
    58  		panic(fmt.Sprintf("could not create lru cache (size = %d)", g.Env.GetUPAKCacheSize()))
    59  	}
    60  	return &CachedUPAKLoader{
    61  		Contextified: NewContextified(g),
    62  		Freshness:    f,
    63  		cache:        c,
    64  		noCache:      false,
    65  		locktab:      NewLockTable(),
    66  	}
    67  }
    68  
    69  func (u *CachedUPAKLoader) LoginAs(uid keybase1.UID) (err error) {
    70  	u.Lock()
    71  	defer u.Unlock()
    72  	u.currentUID = uid
    73  	return nil
    74  }
    75  
    76  func (u *CachedUPAKLoader) GetCurrentUID() keybase1.UID {
    77  	u.Lock()
    78  	defer u.Unlock()
    79  	return u.currentUID
    80  }
    81  
    82  func (u *CachedUPAKLoader) OnLogout() (err error) {
    83  	return u.LoginAs(keybase1.UID(""))
    84  }
    85  
    86  // NewUncachedUPAKLoader creates a UPAK loader that doesn't do any caching.
    87  // It uses the implementation of CachedUPAKLoader but disables all caching.
    88  func NewUncachedUPAKLoader(g *GlobalContext) UPAKLoader {
    89  	return &CachedUPAKLoader{
    90  		Contextified: NewContextified(g),
    91  		Freshness:    time.Duration(0),
    92  		noCache:      true,
    93  	}
    94  }
    95  
    96  func culDBKeyV1(uid keybase1.UID) DbKey {
    97  	return DbKeyUID(DBUserPlusAllKeysV1, uid)
    98  }
    99  
   100  func culDBKeyVersioned(version int, uid keybase1.UID, stubMode StubMode) DbKey {
   101  
   102  	typ := DBUserPlusKeysVersioned
   103  	if stubMode == StubModeUnstubbed {
   104  		typ = DBUserPlusKeysVersionedUnstubbed
   105  	}
   106  	return DbKey{
   107  		Typ: ObjType(typ),
   108  		Key: fmt.Sprintf("%d:%s", version, uid.String()),
   109  	}
   110  }
   111  
   112  func culDBKeyV2(uid keybase1.UID, stubMode StubMode) DbKey {
   113  	return culDBKeyVersioned(2, uid, stubMode)
   114  }
   115  
   116  func (u *CachedUPAKLoader) ClearMemory() {
   117  	if u.noCache {
   118  		return
   119  	}
   120  	u.purgeMemCache()
   121  }
   122  
   123  const UPK2MinorVersionCurrent = keybase1.UPK2MinorVersion_V6
   124  
   125  func (u *CachedUPAKLoader) getCachedUPAKFromDB(ctx context.Context, uid keybase1.UID, stubMode StubMode) (ret *keybase1.UserPlusKeysV2AllIncarnations) {
   126  	var tmp keybase1.UserPlusKeysV2AllIncarnations
   127  	found, err := u.G().LocalDb.GetInto(&tmp, culDBKeyV2(uid, stubMode))
   128  
   129  	if err != nil {
   130  		u.G().Log.CWarningf(ctx, "trouble accessing UserPlusKeysV2AllIncarnations cache: %s", err)
   131  		return nil
   132  	}
   133  	if !found {
   134  		u.G().VDL.CLogf(ctx, VLog0, "| missed disk cache")
   135  		return nil
   136  	}
   137  	if tmp.MinorVersion != UPK2MinorVersionCurrent {
   138  		u.G().VDL.CLogf(ctx, VLog0, "| found old minor version %d, but wanted %d; will overwrite with fresh UPAK", tmp.MinorVersion, UPK2MinorVersionCurrent)
   139  		return nil
   140  	}
   141  
   142  	u.G().VDL.CLogf(ctx, VLog0, "| hit disk cache (v%d)", tmp.MinorVersion)
   143  	u.putMemCache(ctx, uid, stubMode, tmp)
   144  	return &tmp
   145  }
   146  
   147  func (u *CachedUPAKLoader) getCachedUPAKFromDBMaybeTryBothSlots(ctx context.Context, uid keybase1.UID, stubMode StubMode) (ret *keybase1.UserPlusKeysV2AllIncarnations, finalStubMode StubMode) {
   148  	return pickBetterFromCache(func(stubMode StubMode) *keybase1.UserPlusKeysV2AllIncarnations {
   149  		return u.getCachedUPAKFromDB(ctx, uid, stubMode)
   150  	}, stubMode)
   151  }
   152  
   153  func (u *CachedUPAKLoader) getMemCacheMaybeTryBothSlots(ctx context.Context, uid keybase1.UID, stubMode StubMode) (ret *keybase1.UserPlusKeysV2AllIncarnations, finalStubMode StubMode) {
   154  	return pickBetterFromCache(func(stubMode StubMode) *keybase1.UserPlusKeysV2AllIncarnations {
   155  		return u.getMemCache(ctx, uid, stubMode)
   156  	}, stubMode)
   157  }
   158  
   159  func pickBetterFromCache(getter func(stubMode StubMode) *keybase1.UserPlusKeysV2AllIncarnations, stubMode StubMode) (ret *keybase1.UserPlusKeysV2AllIncarnations, finalStubMode StubMode) {
   160  
   161  	ret = getter(stubMode)
   162  	if stubMode == StubModeUnstubbed {
   163  		return ret, StubModeUnstubbed
   164  	}
   165  
   166  	stubbed := ret
   167  	unstubbed := getter(StubModeUnstubbed)
   168  
   169  	if unstubbed == nil {
   170  		return stubbed, StubModeStubbed
   171  	}
   172  	if stubbed == nil {
   173  		return unstubbed, StubModeUnstubbed
   174  	}
   175  	if unstubbed.IsOlderThan(*stubbed) {
   176  		return stubbed, StubModeStubbed
   177  	}
   178  	return unstubbed, StubModeUnstubbed
   179  }
   180  
   181  func (u *CachedUPAKLoader) getCachedUPAKTryMemThenDisk(ctx context.Context, uid keybase1.UID, stubMode StubMode, info *CachedUserLoadInfo) *keybase1.UserPlusKeysV2AllIncarnations {
   182  	upak, cacheStubMode := u.getMemCacheMaybeTryBothSlots(ctx, uid, stubMode)
   183  
   184  	if upak != nil {
   185  		// Note that below we check the minor version and then discard the cached object if it's
   186  		// stale. But no need in memory, since we'll never have the old version in memory.
   187  		u.G().VDL.CLogf(ctx, VLog0, "| hit memory cache (%s -> %s)", stubMode, cacheStubMode)
   188  		if info != nil {
   189  			info.InCache = true
   190  		}
   191  		return upak
   192  	}
   193  
   194  	upak, cacheStubMode = u.getCachedUPAKFromDBMaybeTryBothSlots(ctx, uid, stubMode)
   195  	if upak == nil {
   196  		u.G().VDL.CLogf(ctx, VLog0, "| missed cache")
   197  		return nil
   198  	}
   199  
   200  	u.G().VDL.CLogf(ctx, VLog0, "| hit disk cache (%s -> %s)", stubMode, cacheStubMode)
   201  	if info != nil {
   202  		info.InDiskCache = true
   203  	}
   204  
   205  	return upak
   206  }
   207  
   208  func (u *CachedUPAKLoader) getCachedUPAK(ctx context.Context, uid keybase1.UID, stubMode StubMode, info *CachedUserLoadInfo) (*keybase1.UserPlusKeysV2AllIncarnations, bool) {
   209  
   210  	if u.Freshness == time.Duration(0) || u.noCache {
   211  		u.G().VDL.CLogf(ctx, VLog0, "| cache miss since cache disabled")
   212  		return nil, false
   213  	}
   214  
   215  	upak := u.getCachedUPAKTryMemThenDisk(ctx, uid, stubMode, info)
   216  
   217  	if upak == nil {
   218  		return nil, true
   219  	}
   220  
   221  	diff := u.G().Clock().Now().Sub(keybase1.FromTime(upak.Uvv.CachedAt))
   222  	fresh := (diff <= u.Freshness)
   223  	if fresh {
   224  		u.G().VDL.CLogf(ctx, VLog0, "| cache hit was fresh (cached %s ago)", diff)
   225  	} else {
   226  		u.G().VDL.CLogf(ctx, VLog0, "| cache hit was stale (by %s)", u.Freshness-diff)
   227  	}
   228  	if upak.Stale {
   229  		u.G().VDL.CLogf(ctx, VLog0, "| object is stale by persisted stale bit")
   230  		fresh = false
   231  	}
   232  
   233  	return upak, fresh
   234  }
   235  
   236  type CachedUserLoadInfo struct {
   237  	InCache      bool
   238  	InDiskCache  bool
   239  	TimedOut     bool
   240  	StaleVersion bool
   241  	LoadedLeaf   bool
   242  	LoadedUser   bool
   243  }
   244  
   245  func (u *CachedUPAKLoader) Disable() {
   246  	u.Freshness = time.Duration(0)
   247  }
   248  
   249  func culDebug(u keybase1.UID) string {
   250  	return fmt.Sprintf("CachedUPAKLoader#Load(%s)", u)
   251  }
   252  
   253  func (u *CachedUPAKLoader) extractDeviceKey(upak keybase1.UserPlusAllKeys, deviceID keybase1.DeviceID) (deviceKey *keybase1.PublicKey, revoked *keybase1.RevokedKey, err error) {
   254  	for i := range upak.Base.RevokedDeviceKeys {
   255  		r := &upak.Base.RevokedDeviceKeys[i]
   256  		pk := &r.Key
   257  		if pk.DeviceID == deviceID {
   258  			deviceKey = pk
   259  			revoked = r
   260  		}
   261  	}
   262  	for i := range upak.Base.DeviceKeys {
   263  		pk := &upak.Base.DeviceKeys[i]
   264  		if pk.DeviceID == deviceID {
   265  			deviceKey = pk
   266  			revoked = nil
   267  		}
   268  	}
   269  
   270  	if deviceKey == nil {
   271  		dkey := fmt.Sprintf("%s:%s", upak.Base.Uid, deviceID)
   272  		return nil, nil, fmt.Errorf("device not found for %s", dkey)
   273  	}
   274  
   275  	return deviceKey, revoked, nil
   276  }
   277  
   278  func (u *CachedUPAKLoader) putUPAKToDB(ctx context.Context, uid keybase1.UID, stubMode StubMode, obj keybase1.UserPlusKeysV2AllIncarnations) (err error) {
   279  	err = u.G().LocalDb.PutObj(culDBKeyV2(uid, stubMode), nil, obj)
   280  	if err != nil {
   281  		u.G().Log.CWarningf(ctx, "Error in writing UPAK for %s %s: %s", uid, stubMode, err)
   282  	}
   283  	return err
   284  }
   285  
   286  func (u *CachedUPAKLoader) putUPAKToCache(ctx context.Context, obj *keybase1.UserPlusKeysV2AllIncarnations, stubMode StubMode) (err error) {
   287  
   288  	if u.noCache {
   289  		u.G().VDL.CLogf(ctx, VLog0, "| no cache enabled, so not putting UPAK")
   290  		return nil
   291  	}
   292  
   293  	uid := obj.Current.Uid
   294  	u.G().VDL.CLogf(ctx, VLog0, "| Caching UPAK for %s %s", uid, stubMode)
   295  
   296  	// At this point, we've gone to the server, and we checked that the user is fresh, so if we previously had a stale
   297  	// bit set for this user, we'll turn it off now.
   298  	if obj.Stale {
   299  		u.G().VDL.CLogf(ctx, VLog0, "| resetting stale bit to false for %s %s", uid, stubMode)
   300  		obj.Stale = false
   301  	}
   302  
   303  	existing := u.getMemCache(ctx, uid, stubMode)
   304  
   305  	if existing != nil && obj.IsOlderThan(*existing) {
   306  		u.G().VDL.CLogf(ctx, VLog0, "| CachedUpakLoader#putUPAKToCache: Refusing to overwrite with stale object")
   307  		return errors.New("stale object rejected")
   308  	}
   309  	u.putMemCache(ctx, uid, stubMode, *obj)
   310  
   311  	err = u.putUPAKToDB(ctx, uid, stubMode, *obj)
   312  
   313  	u.deleteV1UPAK(uid)
   314  	return err
   315  }
   316  
   317  func (u *CachedUPAKLoader) PutUserToCache(ctx context.Context, user *User) error {
   318  	lock := u.locktab.AcquireOnName(ctx, u.G(), user.GetUID().String())
   319  	defer lock.Release(ctx)
   320  	upak, err := user.ExportToUPKV2AllIncarnations()
   321  	if err != nil {
   322  		return err
   323  	}
   324  	upak.Uvv.CachedAt = keybase1.ToTime(u.G().Clock().Now())
   325  	err = u.putUPAKToCache(ctx, upak, StubModeFromUnstubbedBool(upak.Current.Unstubbed))
   326  	return err
   327  }
   328  
   329  func (u *CachedUPAKLoader) LoadLite(arg LoadUserArg) (*keybase1.UPKLiteV1AllIncarnations, error) {
   330  	m, tbs := arg.m.WithTimeBuckets()
   331  	arg.m = m
   332  	defer tbs.Record("CachedUPAKLoader.LoadLite")()
   333  	g := m.G()
   334  	uid := arg.uid
   335  
   336  	if uid.IsNil() {
   337  		return nil, errors.New("need a UID to load a UPAK lite")
   338  	}
   339  	lock := u.locktab.AcquireOnName(m.ctx, g, uid.String())
   340  	defer func() {
   341  		lock.Release(m.ctx)
   342  	}()
   343  
   344  	upakLite, err := LoadUPAKLite(arg)
   345  	return upakLite, err
   346  }
   347  
   348  // loadWithInfo loads a user from the CachedUPAKLoader object. The 'info'
   349  // object contains information about how the request was handled, but otherwise,
   350  // this method behaves like (and implements) the public CachedUPAKLoader#Load
   351  // method below. If `accessor` is nil, then a deep copy of the UPAK is returned.
   352  // In some cases, that deep copy can be expensive, so as for users who have lots of
   353  // followees. So if you provide accessor, the UPAK won't be deep-copied, but you'll
   354  // be able to access it from inside the accessor with exclusion.
   355  func (u *CachedUPAKLoader) loadWithInfo(arg LoadUserArg, info *CachedUserLoadInfo, accessor func(k *keybase1.UserPlusKeysV2AllIncarnations) error, shouldReturnFullUser bool) (ret *keybase1.UserPlusKeysV2AllIncarnations, user *User, err error) {
   356  
   357  	// Add a LU= tax to this context, for all subsequent debugging
   358  	arg = arg.EnsureCtxAndLogTag()
   359  
   360  	// Shorthands
   361  	m, tbs := arg.m.WithTimeBuckets()
   362  	g := m.G()
   363  	ctx := m.Ctx()
   364  
   365  	defer m.VTrace(VLog0, culDebug(arg.uid), &err)()
   366  
   367  	if arg.uid.IsNil() {
   368  		if len(arg.name) == 0 {
   369  			return nil, nil, errors.New("need a UID or username to load UPAK from loader")
   370  		}
   371  		// Modifies the load arg, setting a UID
   372  		arg.uid, err = u.LookupUID(ctx, NewNormalizedUsername(arg.name))
   373  		if err != nil {
   374  			return nil, nil, err
   375  		}
   376  	}
   377  
   378  	var lock *NamedLock
   379  	if !arg.cachedOnly {
   380  		lock = u.locktab.AcquireOnName(ctx, g, arg.uid.String())
   381  	}
   382  
   383  	defer func() {
   384  		if lock != nil {
   385  			lock.Release(ctx)
   386  		}
   387  
   388  		if !shouldReturnFullUser {
   389  			user = nil
   390  		}
   391  		if user != nil && err == nil {
   392  			// Update the full-self cacher after the lock is released, to avoid
   393  			// any circular locking.
   394  			if fs := g.GetFullSelfer(); fs != nil && arg.self {
   395  				_ = fs.Update(ctx, user)
   396  			}
   397  		}
   398  	}()
   399  
   400  	returnUPAK := func(upak *keybase1.UserPlusKeysV2AllIncarnations, needCopy bool) (*keybase1.UserPlusKeysV2AllIncarnations, *User, error) {
   401  		if accessor != nil {
   402  			err := accessor(upak)
   403  			if err != nil {
   404  				return nil, nil, err
   405  			}
   406  			return nil, user, err
   407  		}
   408  		if needCopy {
   409  			fin := tbs.Record("CachedUPAKLoader.DeepCopy")
   410  			tmp := upak.DeepCopy()
   411  			upak = &tmp
   412  			fin()
   413  		}
   414  		return upak, user, nil
   415  	}
   416  
   417  	var upak *keybase1.UserPlusKeysV2AllIncarnations
   418  	var fresh bool
   419  
   420  	if !arg.forceReload {
   421  		upak, fresh = u.getCachedUPAK(ctx, arg.uid, arg.stubMode, info)
   422  	}
   423  
   424  	// cached UPAK is fresh or allowed to be stale, and we're not forcing a poll.
   425  	if upak != nil && !arg.forcePoll && (fresh || arg.staleOK) {
   426  		return returnUPAK(upak, true)
   427  	}
   428  
   429  	// If we had a cached UPAK we could return, we'd have already returned it.
   430  	if arg.cachedOnly {
   431  		var message string
   432  		if upak == nil {
   433  			message = "no cached user found"
   434  		} else {
   435  			message = "cached user found, but it was stale, and cached only"
   436  		}
   437  		return nil, nil, UserNotFoundError{UID: arg.uid, Msg: message}
   438  	}
   439  
   440  	if upak != nil {
   441  		// At this point, we have a cached UPAK but we are not confident about whether we can return it.
   442  		// Ask the server whether it is fresh.
   443  
   444  		if arg.forcePoll {
   445  			g.VDL.CLogf(ctx, VLog0, "%s: force-poll required us to repoll (fresh=%v)", culDebug(arg.uid), fresh)
   446  		}
   447  
   448  		if info != nil {
   449  			info.TimedOut = true
   450  		}
   451  
   452  		var sigHints *SigHints
   453  		var leaf *MerkleUserLeaf
   454  
   455  		sigHints, leaf, err = lookupSigHintsAndMerkleLeaf(m, arg.uid, true, arg.ToMerkleOpts())
   456  		if err != nil {
   457  			if arg.staleOK {
   458  				g.VDL.CLogf(ctx, VLog0, "Got error %+v when checking for staleness; using cached UPAK", err)
   459  				return returnUPAK(upak, true)
   460  			}
   461  			return nil, nil, err
   462  		}
   463  
   464  		if info != nil {
   465  			info.LoadedLeaf = true
   466  		}
   467  
   468  		if leaf.eldest == "" {
   469  			g.VDL.CLogf(ctx, VLog0, "%s: cache-hit; but user is nuked, evicting", culDebug(arg.uid))
   470  
   471  			// Our cached user turned out to be in reset state (without
   472  			// current sigchain), remove from cache, and then fall
   473  			// through. LoadUser shall return an error, which we will
   474  			// return to the caller.
   475  			u.removeMemCache(ctx, arg.uid, arg.stubMode)
   476  
   477  			err := u.G().LocalDb.Delete(culDBKeyV2(arg.uid, arg.stubMode))
   478  			if err != nil {
   479  				u.G().Log.Warning("Failed to remove %s from disk cache: %s", arg.uid, err)
   480  			}
   481  			u.deleteV1UPAK(arg.uid)
   482  		} else if leaf.public != nil && leaf.public.Seqno == keybase1.Seqno(upak.Uvv.SigChain) {
   483  			g.VDL.CLogf(ctx, VLog0, "%s: cache-hit; fresh after poll", culDebug(arg.uid))
   484  
   485  			upak.Uvv.CachedAt = keybase1.ToTime(g.Clock().Now())
   486  			// This is only necessary to update the levelDB representation,
   487  			// since the previous line updates the in-memory cache satisfactorily.
   488  			if err := u.putUPAKToCache(ctx, upak, arg.stubMode); err != nil {
   489  				u.G().Log.CDebugf(ctx, "continuing past error in putUPAKToCache: %s", err)
   490  			}
   491  
   492  			return returnUPAK(upak, true)
   493  		}
   494  
   495  		if info != nil {
   496  			info.StaleVersion = true
   497  		}
   498  		arg.sigHints = sigHints
   499  		arg.merkleLeaf = leaf
   500  	}
   501  
   502  	g.VDL.CLogf(ctx, VLog0, "%s: LoadUser", culDebug(arg.uid))
   503  	user, err = LoadUser(arg)
   504  	if info != nil {
   505  		info.LoadedUser = true
   506  	}
   507  
   508  	if user != nil {
   509  		// The `err` value might be non-nil above! Don't overwrite it.
   510  		var exportErr error
   511  		ret, exportErr = user.ExportToUPKV2AllIncarnations()
   512  		if exportErr != nil {
   513  			return nil, nil, exportErr
   514  		}
   515  		ret.Uvv.CachedAt = keybase1.ToTime(g.Clock().Now())
   516  	}
   517  
   518  	// In some cases, it's OK to have a user object and an error. This comes up in
   519  	// Identify2 when identifying users who don't have a sigchain. Note that we'll never
   520  	// hit the cache in this case (for now...)
   521  	//
   522  	// Additionally, we might have an error, a cached UPAK, no user object. If
   523  	// stale is OK, we'll just return the stale data instead of the error.
   524  	if err != nil {
   525  		if upak != nil && arg.staleOK {
   526  			g.VDL.CLogf(ctx, VLog0, "Got error %+v when fetching UPAK, so using cached data", err)
   527  			return returnUPAK(upak, true)
   528  		}
   529  		return ret, user, err
   530  	}
   531  
   532  	if user == nil {
   533  		return nil, nil, UserNotFoundError{UID: arg.uid, Msg: "LoadUser failed"}
   534  	}
   535  
   536  	if err := u.putUPAKToCache(ctx, ret, arg.stubMode); err != nil {
   537  		m.Debug("continuing past error in putUPAKToCache: %s", err)
   538  	}
   539  
   540  	if u.TestDeadlocker != nil {
   541  		u.TestDeadlocker()
   542  	}
   543  
   544  	return returnUPAK(ret, false)
   545  }
   546  
   547  // Load a UserPlusKeysV2AllIncarnations from the local cache, falls back to
   548  // LoadUser, and cache the user. Can only perform lookups by UID. Will return a
   549  // non-nil UserPlusKeysV2AllIncarnations, or a non-nil error, but never both
   550  // non-nil, nor never both nil. If we had to do a full LoadUser as part of the
   551  // request, it's returned too. Convert to UserPlusAllKeys on the way out, for
   552  // backwards compatibility.
   553  func (u *CachedUPAKLoader) Load(arg LoadUserArg) (*keybase1.UserPlusAllKeys, *User, error) {
   554  	ret, user, err := u.loadWithInfo(arg, nil, nil, true)
   555  
   556  	// NOTE -- it's OK to return an error and a user, since certain code paths
   557  	// want both (see note in loadWithInfo).
   558  	var converted *keybase1.UserPlusAllKeys
   559  	if ret != nil {
   560  		tmp := keybase1.UPAKFromUPKV2AI(*ret)
   561  		converted = &tmp
   562  	}
   563  
   564  	return converted, user, err
   565  }
   566  
   567  // Load a UserPlusKeysV2AllIncarnations from the local cache, falls back to
   568  // LoadUser, and cache the user. Can only perform lookups by UID. Will return a
   569  // non-nil UserPlusKeysV2AllIncarnations, or a non-nil error, but never both
   570  // non-nil, nor never both nil. If we had to do a full LoadUser as part of the
   571  // request, it's returned too.
   572  func (u *CachedUPAKLoader) LoadV2(arg LoadUserArg) (*keybase1.UserPlusKeysV2AllIncarnations, *User, error) {
   573  	m, tbs := arg.m.WithTimeBuckets()
   574  	arg.m = m
   575  	defer tbs.Record("CachedUPAKLoader.LoadV2")()
   576  	return u.loadWithInfo(arg, nil, nil, true)
   577  }
   578  
   579  func (u *CachedUPAKLoader) CheckKIDForUID(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (found bool, revokedAt *keybase1.KeybaseTime, deleted bool, err error) {
   580  
   581  	var info CachedUserLoadInfo
   582  	larg := NewLoadUserByUIDArg(ctx, u.G(), uid).WithPublicKeyOptional()
   583  	upak, _, err := u.loadWithInfo(larg, &info, nil, false)
   584  
   585  	if err != nil {
   586  		return false, nil, false, err
   587  	}
   588  	found, revokedAt, deleted = CheckKID(upak, kid)
   589  	if found || info.LoadedLeaf || info.LoadedUser {
   590  		return found, revokedAt, deleted, nil
   591  	}
   592  	larg = larg.WithForceReload()
   593  	upak, _, err = u.loadWithInfo(larg, nil, nil, false)
   594  	if err != nil {
   595  		return false, nil, false, err
   596  	}
   597  	found, revokedAt, deleted = CheckKID(upak, kid)
   598  	return found, revokedAt, deleted, nil
   599  }
   600  
   601  func (u *CachedUPAKLoader) LoadUserPlusKeys(ctx context.Context, uid keybase1.UID, pollForKID keybase1.KID) (keybase1.UserPlusKeys, error) {
   602  	var up keybase1.UserPlusKeys
   603  	if uid.IsNil() {
   604  		return up, NoUIDError{}
   605  	}
   606  
   607  	arg := NewLoadUserArg(u.G()).WithUID(uid).WithPublicKeyOptional().WithNetContext(ctx)
   608  	forcePollValues := []bool{false, true}
   609  
   610  	for _, fp := range forcePollValues {
   611  
   612  		arg = arg.WithForcePoll(fp)
   613  
   614  		upak, _, err := u.Load(arg)
   615  		if err != nil {
   616  			return up, err
   617  		}
   618  		if upak == nil {
   619  			return up, fmt.Errorf("Nil user, nil error from LoadUser")
   620  		}
   621  		up = upak.Base
   622  		if pollForKID.IsNil() || up.FindKID(pollForKID) != nil {
   623  			break
   624  		}
   625  
   626  	}
   627  	return up, nil
   628  }
   629  
   630  // LoadKeyV2 looks through all incarnations for the user and returns the incarnation with the given
   631  // KID, as well as the Key data associated with that KID. It picks the latest such
   632  // incarnation if there are multiple.
   633  func (u *CachedUPAKLoader) LoadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (ret *keybase1.UserPlusKeysV2,
   634  	upak *keybase1.UserPlusKeysV2AllIncarnations, key *keybase1.PublicKeyV2NaCl, err error) {
   635  	ctx = WithLogTag(ctx, "LK") // Load key
   636  	defer u.G().CVTrace(ctx, VLog0, fmt.Sprintf("LoadKeyV2 uid:%s,kid:%s", uid, kid), &err)()
   637  	ctx, tbs := u.G().CTimeBuckets(ctx)
   638  	defer tbs.Record("CachedUPAKLoader.LoadKeyV2")()
   639  	if uid.IsNil() {
   640  		return nil, nil, nil, NoUIDError{}
   641  	}
   642  
   643  	argBase := NewLoadUserArg(u.G()).WithUID(uid).WithPublicKeyOptional().WithNetContext(ctx)
   644  
   645  	// Make the retry mechanism increasingly aggressive. See CORE-8851.
   646  	// It should be that a ForcePoll is good enough, but in some rare cases,
   647  	// people have cached values for previous pre-reset user incarnations that
   648  	// were incorrect. So clobber over that if it comes to it.
   649  	attempts := []LoadUserArg{
   650  		argBase,
   651  		argBase.WithForcePoll(true),
   652  		argBase.WithForceReload(),
   653  	}
   654  
   655  	for i, arg := range attempts {
   656  
   657  		if i > 0 {
   658  			u.G().VDL.CLogf(ctx, VLog0, "| reloading with arg: %s", arg.String())
   659  		}
   660  
   661  		upak, _, err := u.LoadV2(arg)
   662  		if err != nil {
   663  			return nil, nil, nil, err
   664  		}
   665  		if upak == nil {
   666  			return nil, nil, nil, fmt.Errorf("Nil user, nil error from LoadUser")
   667  		}
   668  
   669  		ret, key := upak.FindKID(kid)
   670  		if key != nil {
   671  			u.G().VDL.CLogf(ctx, VLog1, "- found kid in UPAK: %v", ret.Uid)
   672  			return ret, upak, key, nil
   673  		}
   674  	}
   675  
   676  	return nil, nil, nil, NotFoundError{Msg: "Not found: Key for user"}
   677  }
   678  
   679  func (u *CachedUPAKLoader) markStale(ctx context.Context, uid keybase1.UID, mode StubMode) (err error) {
   680  	u.G().VDL.CLogf(ctx, VLog0, "| CachedUPAKLoader#markStale(%s,%v)", uid, mode)
   681  	obj := u.getCachedUPAKFromDB(ctx, uid, mode)
   682  	if obj == nil {
   683  		u.G().VDL.CLogf(ctx, VLog0, "| markStale: cached user object not found")
   684  		return nil
   685  	}
   686  	obj.Stale = true
   687  	err = u.putUPAKToDB(ctx, uid, mode, *obj)
   688  	return err
   689  }
   690  
   691  func (u *CachedUPAKLoader) Invalidate(ctx context.Context, uid keybase1.UID) {
   692  
   693  	u.G().VDL.CLogf(ctx, VLog0, "| CachedUPAKLoader#Invalidate(%s)", uid)
   694  
   695  	if u.noCache {
   696  		return
   697  	}
   698  
   699  	lock := u.locktab.AcquireOnName(ctx, u.G(), uid.String())
   700  	defer lock.Release(ctx)
   701  
   702  	for _, sm := range []StubMode{StubModeStubbed, StubModeUnstubbed} {
   703  		err := u.markStale(ctx, uid, sm)
   704  		if err != nil {
   705  			u.G().Log.CWarningf(ctx, "Failed to remove %s from disk cache: %s", uid, err)
   706  		}
   707  	}
   708  
   709  	// Do this after invalidation, since loading upaks from DB puts them into memory
   710  	u.removeMemCache(ctx, uid, StubModeUnstubbed)
   711  	u.removeMemCache(ctx, uid, StubModeStubbed)
   712  
   713  	u.deleteV1UPAK(uid)
   714  }
   715  
   716  // Load the PublicKey for a user's device from the local cache, falling back to LoadUser, and cache the user.
   717  // If the user exists but the device doesn't, will force a load in case the device is very new.
   718  func (u *CachedUPAKLoader) LoadDeviceKey(ctx context.Context, uid keybase1.UID, deviceID keybase1.DeviceID) (upakv1 *keybase1.UserPlusAllKeys, deviceKey *keybase1.PublicKey, revoked *keybase1.RevokedKey, err error) {
   719  	var info CachedUserLoadInfo
   720  	larg := NewLoadUserByUIDArg(ctx, u.G(), uid)
   721  	upakV2, _, err := u.loadWithInfo(larg, &info, nil, false)
   722  	if err != nil {
   723  		return nil, nil, nil, err
   724  	}
   725  	upakV1 := keybase1.UPAKFromUPKV2AI(*upakV2)
   726  
   727  	deviceKey, revoked, err = u.extractDeviceKey(upakV1, deviceID)
   728  	if err == nil {
   729  		// Early success, return
   730  		return &upakV1, deviceKey, revoked, err
   731  	}
   732  
   733  	// Try again with a forced load in case the device is very new.
   734  	larg = larg.WithForcePoll(true)
   735  	upakV2, _, err = u.loadWithInfo(larg, nil, nil, false)
   736  	if err != nil {
   737  		return nil, nil, nil, err
   738  	}
   739  	upakV1 = keybase1.UPAKFromUPKV2AI(*upakV2)
   740  
   741  	deviceKey, revoked, err = u.extractDeviceKey(upakV1, deviceID)
   742  	return &upakV1, deviceKey, revoked, err
   743  }
   744  
   745  // If the user exists but the device doesn't, will force a load in case the device is very new.
   746  func (u *CachedUPAKLoader) LoadUPAKWithDeviceID(ctx context.Context, uid keybase1.UID, deviceID keybase1.DeviceID) (*keybase1.UserPlusKeysV2AllIncarnations, error) {
   747  	var info CachedUserLoadInfo
   748  	larg := NewLoadUserByUIDArg(ctx, u.G(), uid).WithPublicKeyOptional()
   749  	upakV2, _, err := u.loadWithInfo(larg, &info, nil, false)
   750  	if err != nil {
   751  		return nil, err
   752  	}
   753  
   754  	for _, device := range upakV2.Current.DeviceKeys {
   755  		if device.DeviceID.Eq(deviceID) {
   756  			// Early success, return
   757  			return upakV2, nil
   758  		}
   759  	}
   760  
   761  	// Try again with a forced load in case the device is very new.
   762  	larg = larg.WithForcePoll(true)
   763  	upakV2, _, err = u.loadWithInfo(larg, nil, nil, false)
   764  	if err != nil {
   765  		return nil, err
   766  	}
   767  	return upakV2, nil
   768  }
   769  
   770  // LookupUsername uses the UIDMapper to find a username for uid.
   771  func (u *CachedUPAKLoader) LookupUsername(ctx context.Context, uid keybase1.UID) (NormalizedUsername, error) {
   772  	var empty NormalizedUsername
   773  	uids := []keybase1.UID{uid}
   774  	namePkgs, err := u.G().UIDMapper.MapUIDsToUsernamePackages(ctx, u.G(), uids, 0, 0, false)
   775  	if err != nil {
   776  		return empty, err
   777  	}
   778  	if len(namePkgs) == 0 {
   779  		return empty, UserNotFoundError{UID: uid, Msg: "in CachedUPAKLoader"}
   780  	}
   781  
   782  	if u.TestDeadlocker != nil {
   783  		u.TestDeadlocker()
   784  	}
   785  
   786  	return namePkgs[0].NormalizedUsername, nil
   787  }
   788  
   789  // LookupUsernameUPAK uses the upak loader to find a username for uid.
   790  func (u *CachedUPAKLoader) LookupUsernameUPAK(ctx context.Context, uid keybase1.UID) (NormalizedUsername, error) {
   791  	var info CachedUserLoadInfo
   792  	arg := NewLoadUserByUIDArg(ctx, u.G(), uid).WithStaleOK(true).WithPublicKeyOptional()
   793  	var ret NormalizedUsername
   794  	_, _, err := u.loadWithInfo(arg, &info, func(upak *keybase1.UserPlusKeysV2AllIncarnations) error {
   795  		if upak == nil {
   796  			return UserNotFoundError{UID: uid, Msg: "in CachedUPAKLoader"}
   797  		}
   798  		ret = NewNormalizedUsername(upak.Current.Username)
   799  		return nil
   800  	}, false)
   801  	return ret, err
   802  }
   803  
   804  // LookupUID is a verified map of username -> UID. IT calls into the resolver, which gives un untrusted
   805  // UID, but verifies with the UPAK loader that the mapping UID -> username is correct.
   806  func (u *CachedUPAKLoader) LookupUID(ctx context.Context, un NormalizedUsername) (keybase1.UID, error) {
   807  	m := NewMetaContext(ctx, u.G())
   808  	rres := u.G().Resolver.Resolve(m, un.String())
   809  	if err := rres.GetError(); err != nil {
   810  		return keybase1.UID(""), err
   811  	}
   812  	un2, err := u.LookupUsername(ctx, rres.GetUID())
   813  	if err != nil {
   814  		return keybase1.UID(""), err
   815  	}
   816  	if !un.Eq(un2) {
   817  		m.Warning("Unexpected mismatched usernames (uid=%s): %s != %s", rres.GetUID(), un.String(), un2.String())
   818  		return keybase1.UID(""), NewBadUsernameError(un.String())
   819  	}
   820  	return rres.GetUID(), nil
   821  }
   822  
   823  func (u *CachedUPAKLoader) lookupUsernameAndDeviceWithInfo(ctx context.Context, uid keybase1.UID, did keybase1.DeviceID, info *CachedUserLoadInfo) (username NormalizedUsername, deviceName string, deviceType keybase1.DeviceTypeV2, err error) {
   824  	arg := NewLoadUserByUIDArg(ctx, u.G(), uid)
   825  
   826  	// First iteration through, say it's OK to load a stale user. Note that the
   827  	// mappings of UID to Username and DeviceID to DeviceName are immutable, so this
   828  	// data can never be stale. However, our user might be out of date and lack the
   829  	// mappings, so the second time through, we request a fresh object.
   830  	staleOK := []bool{true, false}
   831  	for _, b := range staleOK {
   832  		arg = arg.WithStaleOK(b)
   833  		found := false
   834  		_, _, err := u.loadWithInfo(arg, info, func(upak *keybase1.UserPlusKeysV2AllIncarnations) error {
   835  			if upak == nil {
   836  				return nil
   837  			}
   838  			if pk := upak.FindDevice(did); pk != nil {
   839  				username = NewNormalizedUsername(upak.Current.Username)
   840  				deviceName = pk.DeviceDescription
   841  				deviceType = pk.DeviceType
   842  				found = true
   843  			}
   844  			return nil
   845  		}, false)
   846  		if err != nil {
   847  			return NormalizedUsername(""), "", keybase1.DeviceTypeV2_NONE, err
   848  		}
   849  		if found {
   850  			return username, deviceName, deviceType, nil
   851  		}
   852  	}
   853  	err = NotFoundError{fmt.Sprintf("UID/Device pair %s/%s not found", uid, did)}
   854  	return NormalizedUsername(""), "", keybase1.DeviceTypeV2_NONE, err
   855  }
   856  
   857  func (u *CachedUPAKLoader) CheckDeviceForUIDAndUsername(ctx context.Context, uid keybase1.UID, did keybase1.DeviceID, n NormalizedUsername, suppressNetworkErrs bool) (err error) {
   858  	arg := NewLoadUserByUIDArg(ctx, u.G(), uid).WithForcePoll(true).WithPublicKeyOptional().WithStaleOK(suppressNetworkErrs)
   859  	foundUser := false
   860  	foundDevice := false
   861  	isRevoked := false
   862  	var foundUsername NormalizedUsername
   863  	_, _, err = u.loadWithInfo(arg, nil, func(upak *keybase1.UserPlusKeysV2AllIncarnations) error {
   864  		if upak == nil {
   865  			return nil
   866  		}
   867  		foundUser = true
   868  		foundUsername = NewNormalizedUsername(upak.Current.Username)
   869  		if pk := upak.FindDevice(did); pk != nil {
   870  			foundDevice = true
   871  			if pk.Base.Revocation != nil {
   872  				isRevoked = true
   873  			}
   874  		}
   875  		return nil
   876  	}, false)
   877  	if err != nil {
   878  		return err
   879  	}
   880  	if !foundUser {
   881  		return UserNotFoundError{UID: uid}
   882  	}
   883  	if !foundDevice {
   884  		return DeviceNotFoundError{Where: "UPAKLoader", ID: did, Loaded: false}
   885  	}
   886  	if isRevoked {
   887  		return NewKeyRevokedError(did.String())
   888  	}
   889  	if !n.IsNil() && !foundUsername.Eq(n) {
   890  		return LoggedInWrongUserError{ExistingName: foundUsername, AttemptedName: n}
   891  	}
   892  	return nil
   893  }
   894  
   895  func (u *CachedUPAKLoader) loadUserWithKIDAndInfo(ctx context.Context, uid keybase1.UID, kid keybase1.KID, info *CachedUserLoadInfo) (ret *keybase1.UserPlusKeysV2AllIncarnations, err error) {
   896  	argBase := NewLoadUserArg(u.G()).WithUID(uid).WithPublicKeyOptional().WithNetContext(ctx)
   897  
   898  	// See comment in LoadKeyV2
   899  	attempts := []LoadUserArg{
   900  		argBase,
   901  		argBase.WithForcePoll(true),
   902  		argBase.WithForceReload(),
   903  	}
   904  	for _, arg := range attempts {
   905  		u.G().VDL.CLogf(ctx, VLog0, "| loadWithUserKIDAndInfo: loading with arg: %s", arg.String())
   906  		ret, _, err = u.loadWithInfo(arg, info, nil, false)
   907  		if err == nil && ret != nil && (kid.IsNil() || ret.HasKID(kid)) {
   908  			u.G().VDL.CLogf(ctx, VLog0, "| loadWithUserKIDAndInfo: UID/KID %s/%s found", uid, kid)
   909  			return ret, nil
   910  		}
   911  	}
   912  	if err == nil {
   913  		err = NotFoundError{fmt.Sprintf("UID/KID pair %s/%s not found", uid, kid)}
   914  	}
   915  	return nil, err
   916  }
   917  
   918  func (u *CachedUPAKLoader) LoadV2WithKID(ctx context.Context, uid keybase1.UID, kid keybase1.KID) (*keybase1.UserPlusKeysV2AllIncarnations, error) {
   919  	return u.loadUserWithKIDAndInfo(ctx, uid, kid, nil)
   920  }
   921  
   922  func (u *CachedUPAKLoader) LookupUsernameAndDevice(ctx context.Context, uid keybase1.UID, did keybase1.DeviceID) (username NormalizedUsername, deviceName string, deviceType keybase1.DeviceTypeV2, err error) {
   923  	return u.lookupUsernameAndDeviceWithInfo(ctx, uid, did, nil)
   924  }
   925  
   926  // v1 UPAKs are all legacy and need to be gradually cleaned from cache.
   927  func (u *CachedUPAKLoader) deleteV1UPAK(uid keybase1.UID) {
   928  	err := u.G().LocalDb.Delete(culDBKeyV1(uid))
   929  	if err != nil {
   930  		u.G().Log.Warning("Failed to remove %s v1 object from disk cache: %s", uid, err)
   931  	}
   932  }
   933  
   934  func memCacheKey(u keybase1.UID, stubMode StubMode) string {
   935  	return fmt.Sprintf("%s-%s", u, stubMode)
   936  }
   937  
   938  func (u *CachedUPAKLoader) getMemCache(ctx context.Context, uid keybase1.UID, stubMode StubMode) *keybase1.UserPlusKeysV2AllIncarnations {
   939  	val, ok := u.cache.Get(memCacheKey(uid, stubMode))
   940  	if !ok {
   941  		return nil
   942  	}
   943  
   944  	upak, ok := val.(keybase1.UserPlusKeysV2AllIncarnations)
   945  	if !ok {
   946  		u.G().Log.CWarningf(ctx, "invalid type in upak cache: %T", val)
   947  		return nil
   948  	}
   949  
   950  	return &upak
   951  }
   952  
   953  func (u *CachedUPAKLoader) putMemCache(ctx context.Context, uid keybase1.UID, stubMode StubMode, upak keybase1.UserPlusKeysV2AllIncarnations) {
   954  	u.cache.Add(memCacheKey(uid, stubMode), upak)
   955  }
   956  
   957  func (u *CachedUPAKLoader) removeMemCache(ctx context.Context, uid keybase1.UID, stubMode StubMode) {
   958  	u.cache.Remove(memCacheKey(uid, stubMode))
   959  }
   960  
   961  func (u *CachedUPAKLoader) purgeMemCache() {
   962  	u.cache.Purge()
   963  }
   964  
   965  func checkDeviceValidForUID(ctx context.Context, u UPAKLoader, uid keybase1.UID, did keybase1.DeviceID) error {
   966  	var nnu NormalizedUsername
   967  	return u.CheckDeviceForUIDAndUsername(ctx, uid, did, nnu, false)
   968  }
   969  
   970  func CheckCurrentUIDDeviceID(m MetaContext) (err error) {
   971  	defer m.Trace("CheckCurrentUIDDeviceID", &err)()
   972  	uid := m.G().Env.GetUID()
   973  	if uid.IsNil() {
   974  		return NoUIDError{}
   975  	}
   976  	did := m.G().Env.GetDeviceIDForUID(uid)
   977  	if did.IsNil() {
   978  		return NoDeviceError{fmt.Sprintf("for UID %s", uid)}
   979  	}
   980  	return checkDeviceValidForUID(m.Ctx(), m.G().GetUPAKLoader(), uid, did)
   981  }
   982  
   983  // Batcher loads a batch of UPAKs with the given window width. It keeps calling getArg(i) with an
   984  // increasing i, until that getArg return nil, in which case the production of UPAK loads is over.
   985  // UPAKs will be loaded and fed into processResult() as they come in. Both getArg() and processResult()
   986  // are called in the same mutex to simplify synchronization.
   987  func (u *CachedUPAKLoader) Batcher(ctx context.Context, getArg func(int) *LoadUserArg, processResult func(int, *keybase1.UserPlusKeysV2AllIncarnations) error, window int) (err error) {
   988  	if window == 0 {
   989  		window = 10
   990  	}
   991  
   992  	ctx = WithLogTag(ctx, "LUB")
   993  	eg, ctx := errgroup.WithContext(ctx)
   994  	defer u.G().CTrace(ctx, "CachedUPAKLoader#Batcher", &err)()
   995  
   996  	type argWithIndex struct {
   997  		i   int
   998  		arg LoadUserArg
   999  	}
  1000  	args := make(chan argWithIndex)
  1001  	var mut sync.Mutex
  1002  
  1003  	// Make a stream of args, and send them down the channel
  1004  	var stopBatch bool
  1005  	eg.Go(func() error {
  1006  		defer close(args)
  1007  		for i := 0; !stopBatch; i++ {
  1008  			mut.Lock()
  1009  			arg := getArg(i)
  1010  			mut.Unlock()
  1011  			if arg == nil {
  1012  				return nil
  1013  			}
  1014  			select {
  1015  			case args <- argWithIndex{i, arg.WithNetContext(ctx)}:
  1016  			case <-ctx.Done():
  1017  				return ctx.Err()
  1018  			}
  1019  		}
  1020  		return nil
  1021  	})
  1022  
  1023  	for i := 0; i < window; i++ {
  1024  		eg.Go(func() (err error) {
  1025  			// If we receive an error, kill the pipeline
  1026  			defer func() {
  1027  				if err != nil {
  1028  					mut.Lock()
  1029  					defer mut.Unlock()
  1030  					u.G().Log.CDebugf(ctx, "CachedUPAKLoader#Batcher unable to complete batch: %v, setting stopBatch=true", err)
  1031  					stopBatch = true
  1032  				}
  1033  			}()
  1034  			for awi := range args {
  1035  				arg := awi.arg
  1036  				_, _, err = u.loadWithInfo(arg, nil, func(u *keybase1.UserPlusKeysV2AllIncarnations) error {
  1037  					if processResult != nil {
  1038  						mut.Lock()
  1039  						err = processResult(awi.i, u)
  1040  						mut.Unlock()
  1041  						if err != nil {
  1042  							return err
  1043  						}
  1044  					}
  1045  					return nil
  1046  				}, false)
  1047  				if err != nil {
  1048  					return err
  1049  				}
  1050  			}
  1051  			return nil
  1052  		})
  1053  	}
  1054  
  1055  	return eg.Wait()
  1056  }