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  }