github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/rekey_queue.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "sync" 9 10 "github.com/keybase/client/go/kbfs/tlf" 11 "github.com/keybase/client/go/logger" 12 "golang.org/x/net/context" 13 "golang.org/x/time/rate" 14 ) 15 16 // When provisioning a new device from an existing device, the provisionee 17 // needs one of the existing devices to rekey for it, or it has to use paperkey 18 // for the rekey. For the case where an existing device does the rekey, there 19 // are three routines which eventually all go through this rekey queue. These 20 // three rekey routines are: 21 // 22 // 1. When a new device is added, the service on provisioner calls an RPC into 23 // KBFS, notifying the latter about the new device (provisionee) and that it 24 // needs rekey. 25 // 2. On KBFS client, a background routine runs once per hour. It asks the 26 // mdserver to check for TLFs that needs rekey. Note that this happens on all 27 // KBFS devices, no matter it has rekey capability or now. 28 // 29 // Both 1 and 2 do this by calling MDServerRemote.CheckForRekeys to send back a 30 // FoldersNeedRekey request. 31 // 32 // 3. When the provisionee gets provisioned, it goes through all TLFs and sends 33 // a MD update for each one of them, by merely copying (since it doesn't have 34 // access to the key yet) the existing MD revision while setting the rekey bit 35 // in the flag. 36 37 const ( 38 numConcurrentRekeys = 64 39 rekeysPerSecond rate.Limit = 16 40 ) 41 42 // RekeyQueueStandard implements the RekeyQueue interface. 43 type RekeyQueueStandard struct { 44 config Config 45 log logger.Logger 46 queue chan tlf.ID 47 limiter *rate.Limiter 48 cancel context.CancelFunc 49 shutdownDoneCh chan struct{} 50 51 mu sync.RWMutex // guards everything below 52 pendings map[tlf.ID]bool 53 } 54 55 // Test that RekeyQueueStandard fully implements the RekeyQueue interface. 56 var _ RekeyQueue = (*RekeyQueueStandard)(nil) 57 58 // NewRekeyQueueStandard creates a new rekey queue. 59 func NewRekeyQueueStandard(config Config) (rkq *RekeyQueueStandard) { 60 ctx, cancel := context.WithCancel(context.Background()) 61 rkq = &RekeyQueueStandard{ 62 config: config, 63 log: config.MakeLogger("RQ"), 64 queue: make(chan tlf.ID, config.Mode().RekeyQueueSize()), 65 limiter: rate.NewLimiter(rekeysPerSecond, numConcurrentRekeys), 66 pendings: make(map[tlf.ID]bool), 67 cancel: cancel, 68 shutdownDoneCh: make(chan struct{}), 69 } 70 if config.Mode().RekeyWorkers() > 0 { 71 rkq.start(ctx) 72 } else { 73 cancel() 74 rkq.cancel = nil 75 } 76 return rkq 77 } 78 79 // start spawns a goroutine that dispatches rekey requests to correct folder 80 // branch ops while conforming to the rater limiter. 81 func (rkq *RekeyQueueStandard) start(ctx context.Context) { 82 go func() { 83 defer close(rkq.shutdownDoneCh) 84 for { 85 select { 86 case id := <-rkq.queue: 87 if err := rkq.limiter.Wait(ctx); err != nil { 88 rkq.log.Debug("Waiting on rate limiter for tlf=%v error: %v", id, err) 89 return 90 } 91 rkq.config.KBFSOps().RequestRekey(context.Background(), id) 92 func(id tlf.ID) { 93 rkq.mu.Lock() 94 defer rkq.mu.Unlock() 95 delete(rkq.pendings, id) 96 }(id) 97 case err := <-ctx.Done(): 98 rkq.log.Debug("Rekey queue background routine context done: %v", err) 99 return 100 } 101 } 102 }() 103 } 104 105 // Enqueue implements the RekeyQueue interface for RekeyQueueStandard. 106 func (rkq *RekeyQueueStandard) Enqueue(id tlf.ID) { 107 rkq.mu.Lock() 108 defer rkq.mu.Unlock() 109 rkq.pendings[id] = true 110 111 select { 112 case rkq.queue <- id: 113 default: 114 // The queue is full; drop this one for now until the next 115 // request to the server for more rekeys. 116 rkq.log.Debug("Rekey queue is full; dropping %s", id) 117 } 118 } 119 120 // IsRekeyPending implements the RekeyQueue interface for RekeyQueueStandard. 121 func (rkq *RekeyQueueStandard) IsRekeyPending(id tlf.ID) bool { 122 rkq.mu.RLock() 123 defer rkq.mu.RUnlock() 124 return rkq.pendings[id] 125 } 126 127 // Shutdown implements the RekeyQueue interface for RekeyQueueStandard. 128 func (rkq *RekeyQueueStandard) Shutdown() { 129 // Don't wait on the shutdown channel while holding the lock, 130 // since that can cause deadlock. Get it first, and then wait 131 // after releasing the lock. 132 cancel, shutdownDoneCh := func() (context.CancelFunc, <-chan struct{}) { 133 rkq.mu.Lock() 134 defer rkq.mu.Unlock() 135 cancel := rkq.cancel 136 rkq.cancel = nil 137 return cancel, rkq.shutdownDoneCh 138 }() 139 140 if cancel != nil { 141 cancel() 142 <-shutdownDoneCh 143 } 144 }