/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/identify3.go
379 строк
9 KB
zoom-ua
gofumpt (#28710)
17 дек 2025, 17:16
Не верифицирован
17 дек 2025, 17:16
cda510b
Код
Авторство
О чём код?
package libkb import ( "encoding/hex" "sync" "time" keybase1 "github.com/keybase/client/go/protocol/keybase1" "golang.org/x/sync/errgroup" ) // Identify3Session corresponds to a single screen showing a user profile. // It maps 1:1 with an Identify2GUIID, and is labeled as such. Here we'll keep // track of whatever context we need to pass across calls, and also the TrackToken // for the final result. type Identify3Session struct { sync.Mutex created time.Time id keybase1.Identify3GUIID outcome *IdentifyOutcome trackBroken bool needUpgrade bool didExpire bool // true if we ran an expire on this session (so we don't repeat) } func NewIdentify3GUIID() (keybase1.Identify3GUIID, error) { var b []byte l := 12 b, err := RandBytes(l) if err != nil { return keybase1.Identify3GUIID(""), err } b[l-1] = 0x34 return keybase1.Identify3GUIID(hex.EncodeToString(b)), nil } func NewIdentify3SessionWithID(mctx MetaContext, id keybase1.Identify3GUIID) *Identify3Session { return &Identify3Session{ created: mctx.G().GetClock().Now(), id: id, } } func NewIdentify3Session(mctx MetaContext) (*Identify3Session, error) { id, err := NewIdentify3GUIID() if err != nil { return nil, err } ret := &Identify3Session{ created: mctx.G().GetClock().Now(), id: id, } mctx.Debug("generated new identify3 session: %s", id) return ret, nil } func (s *Identify3Session) ID() keybase1.Identify3GUIID { s.Lock() defer s.Unlock() return s.id } func (s *Identify3Session) ResultType() keybase1.Identify3ResultType { s.Lock() defer s.Unlock() switch { case s.trackBroken: return keybase1.Identify3ResultType_BROKEN case s.needUpgrade: return keybase1.Identify3ResultType_NEEDS_UPGRADE default: return keybase1.Identify3ResultType_OK } } func (s *Identify3Session) Outcome() *IdentifyOutcome { s.Lock() defer s.Unlock() return s.outcome } func (s *Identify3Session) OutcomeLocked() *IdentifyOutcome { return s.outcome } func (s *Identify3Session) SetTrackBroken() { s.Lock() defer s.Unlock() s.trackBroken = true } func (s *Identify3Session) SetNeedUpgrade() { s.Lock() defer s.Unlock() s.needUpgrade = true } func (s *Identify3Session) SetOutcome(o *IdentifyOutcome) { s.Lock() defer s.Unlock() s.outcome = o } // Identify3State keeps track of all active ID3 state across the whole app. It has // a cache that's periodically cleaned up. type Identify3State struct { sync.Mutex expireCh chan<- struct{} shutdownCh chan struct{} eg errgroup.Group // Table of keybase1.Identify3GUIID -> *identify3Session's cache map[keybase1.Identify3GUIID](*Identify3Session) expirationQueue [](*Identify3Session) defaultWaitTime time.Duration expireTime time.Duration bgThreadTimeMu sync.Mutex testCompletionCh chan<- time.Time shutdownMu sync.Mutex shutdown bool } func NewIdentify3State(g *GlobalContext) *Identify3State { return newIdentify3State(g, nil) } func NewIdentify3StateForTest(g *GlobalContext) (*Identify3State, <-chan time.Time) { ch := make(chan time.Time, 1000) state := newIdentify3State(g, ch) return state, ch } func newIdentify3State(g *GlobalContext, testCompletionCh chan<- time.Time) *Identify3State { expireCh := make(chan struct{}) shutdownCh := make(chan struct{}) ret := &Identify3State{ expireCh: expireCh, shutdownCh: shutdownCh, cache: make(map[keybase1.Identify3GUIID](*Identify3Session)), defaultWaitTime: time.Hour, expireTime: 24 * time.Hour, testCompletionCh: testCompletionCh, } ret.makeNewCache() ret.eg.Go(func() error { return ret.runExpireThread(g, expireCh, shutdownCh) }) ret.pokeExpireThread() return ret } func (s *Identify3State) Shutdown() chan struct{} { ch := make(chan struct{}) if s.markShutdown() { go func() { _ = s.eg.Wait() s.shutdownCh = nil close(ch) }() } else { close(ch) } return ch } func (s *Identify3State) isShutdown() bool { s.shutdownMu.Lock() defer s.shutdownMu.Unlock() return s.shutdown } // markShutdown marks this state as having shutdown. Will return true the first // time through, and false every other time. func (s *Identify3State) markShutdown() bool { s.shutdownMu.Lock() defer s.shutdownMu.Unlock() if s.shutdown { return false } close(s.shutdownCh) s.shutdown = true return true } func (s *Identify3State) makeNewCache() { s.Lock() s.cache = make(map[keybase1.Identify3GUIID](*Identify3Session)) s.expirationQueue = nil s.Unlock() } func (s *Identify3State) OnLogout() { s.makeNewCache() s.pokeExpireThread() } func (s *Identify3State) runExpireThread(g *GlobalContext, expireCh <-chan struct{}, shutdownCh chan struct{}, ) error { mctx := NewMetaContextBackground(g) wait := s.defaultWaitTime nowFn := func() time.Time { return mctx.G().Clock().Now() } now := nowFn() wakeupTime := now.Add(wait) for { select { case <-shutdownCh: mctx.Debug("identify3State#runExpireThread: exiting on shutdown") return nil case <-expireCh: case <-mctx.G().Clock().AfterTime(wakeupTime): mctx.Debug("identify3State#runExpireThread: wakeup after %v timeout (at %v)", wait, wakeupTime) } // Guard all time manipulation in a lock for the purposes of testing. // In real life, this shouldn't matter much, but it can't really hurt. s.bgThreadTimeMu.Lock() now = nowFn() wait = s.expireSessions(mctx, now) wakeupTime = now.Add(wait) s.bgThreadTimeMu.Unlock() // Also for the purposes of test, broadcast how far we've processing in time. // In real life, this will be a noop, since s.testCompletionCh will be nil if s.testCompletionCh != nil { s.testCompletionCh <- now } } } func (s *Identify3Session) doExpireSession(mctx MetaContext) { defer mctx.Trace("Identify3Session#doExpireSession", nil)() s.Lock() defer s.Unlock() mctx.Debug("Identify3Session#doExpireSession(%s)", s.id) if s.didExpire { mctx.Warning("not repeating session expire for %s", s.id) return } s.didExpire = true cli, err := mctx.G().UIRouter.GetIdentify3UI(mctx) if err != nil { mctx.Warning("failed to get an electron UI to expire %s: %s", s.id, err) return } if cli == nil { mctx.Warning("failed to get an electron UI to expire %s: got nil", s.id) return } err = cli.Identify3TrackerTimedOut(mctx.Ctx(), s.id) if err != nil { mctx.Warning("error timing ID3 session %s: %s", s.id, err) } } func (s *Identify3State) expireSessions(mctx MetaContext, now time.Time) time.Duration { defer mctx.Trace("Identify3State#expireSessions", nil)() // getSesionsToExpire holds the Identify3State Mutex. toExpire, diff := s.getSessionsToExpire(mctx, now) // doExpireSessions does not hold the Identify3State Mutex, because it // calls out to the front end via Identify3TrackedTimedOut. s.doExpireSessions(mctx, toExpire) return diff } func (s *Identify3State) doExpireSessions(mctx MetaContext, toExpire []*Identify3Session) { for _, sess := range toExpire { sess.doExpireSession(mctx) } } func (s *Identify3State) getSessionsToExpire(mctx MetaContext, now time.Time) (ret []*Identify3Session, diff time.Duration) { s.Lock() defer s.Unlock() for { if len(s.expirationQueue) == 0 { return ret, s.defaultWaitTime } var sess *Identify3Session sess, diff = s.getSessionToExpire(mctx, now) if diff > 0 { return ret, diff } if sess != nil { ret = append(ret, sess) } } } // getSessionToExpire should be called when holding the Identify3State Mutex. It looks in the // expiration queue and pops off those sessions that are ready to be marked expired. func (s *Identify3State) getSessionToExpire(mctx MetaContext, now time.Time) (*Identify3Session, time.Duration) { sess := s.expirationQueue[0] sess.Lock() defer sess.Unlock() expireAt := sess.created.Add(s.expireTime) diff := expireAt.Sub(now) if diff > 0 { return nil, diff } s.expirationQueue = s.expirationQueue[1:] // Only send the expiration if the session is still in the cache table. // If not, that means it was already acted upon if _, found := s.cache[sess.id]; !found { return nil, diff } mctx.Debug("Identify3State#getSessionToExpire: removing %s", sess.id) s.removeFromTableLocked(sess.id) return sess, diff } // get an identify3Session out of the cache, as keyed by a Identify3GUIID. Return // (nil, nil) if not found. Return (nil, Error) if there was an expected error. // Return (i, nil) if found, where i is the **unlocked** object. func (s *Identify3State) Get(key keybase1.Identify3GUIID) (ret *Identify3Session, err error) { s.Lock() defer s.Unlock() return s.getLocked(key) } func (s *Identify3State) getLocked(key keybase1.Identify3GUIID) (ret *Identify3Session, err error) { ret, found := s.cache[key] if !found { return nil, nil } return ret, nil } func (s *Identify3State) Put(sess *Identify3Session) error { err := s.lockAndPut(sess) s.pokeExpireThread() return err } func (s *Identify3State) lockAndPut(sess *Identify3Session) error { s.Lock() defer s.Unlock() id := sess.ID() tmp, err := s.getLocked(id) if err != nil { return err } if tmp != nil { return ExistsError{Msg: "Identify3 ID already exists"} } s.cache[id] = sess s.expirationQueue = append(s.expirationQueue, sess) return nil } func (s *Identify3State) Remove(key keybase1.Identify3GUIID) { s.Lock() defer s.Unlock() s.removeFromTableLocked(key) } func (s *Identify3State) removeFromTableLocked(key keybase1.Identify3GUIID) { delete(s.cache, key) } // pokeExpireThread should never be called when holding s.Mutex. func (s *Identify3State) pokeExpireThread() { if s.isShutdown() { return } s.expireCh <- struct{}{} }