github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/semaphore_disk_limiter.go (about)

     1  // Copyright 2017 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/kbfssync"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/pkg/errors"
    13  	"golang.org/x/net/context"
    14  )
    15  
    16  type quotaSimpleTracker struct {
    17  	quotaLock      sync.RWMutex
    18  	unflushedBytes int64
    19  	quotaBytes     int64
    20  }
    21  
    22  func (qst *quotaSimpleTracker) getQuotaInfo() (
    23  	usedQuotaBytes, quotaBytes int64) {
    24  	qst.quotaLock.RLock()
    25  	defer qst.quotaLock.RUnlock()
    26  	usedQuotaBytes = qst.unflushedBytes
    27  	quotaBytes = qst.quotaBytes
    28  	return usedQuotaBytes, quotaBytes
    29  }
    30  
    31  func (qst *quotaSimpleTracker) onJournalEnable(unflushedBytes int64) {
    32  	qst.quotaLock.Lock()
    33  	defer qst.quotaLock.Unlock()
    34  	qst.unflushedBytes += unflushedBytes
    35  }
    36  
    37  func (qst *quotaSimpleTracker) onJournalDisable(unflushedBytes int64) {
    38  	qst.quotaLock.Lock()
    39  	defer qst.quotaLock.Unlock()
    40  	qst.unflushedBytes -= unflushedBytes
    41  }
    42  
    43  func (qst *quotaSimpleTracker) afterBlockPut(
    44  	blockBytes int64, putData bool) {
    45  	qst.quotaLock.Lock()
    46  	defer qst.quotaLock.Unlock()
    47  	if putData {
    48  		qst.unflushedBytes += blockBytes
    49  	}
    50  }
    51  
    52  func (qst *quotaSimpleTracker) onBlocksFlush(blockBytes int64) {
    53  	qst.quotaLock.Lock()
    54  	defer qst.quotaLock.Unlock()
    55  	qst.unflushedBytes -= blockBytes
    56  }
    57  
    58  // semaphoreDiskLimiter is an implementation of diskLimiter that uses
    59  // semaphores to limit the byte, file, and quota usage.
    60  type semaphoreDiskLimiter struct {
    61  	byteLimit     int64
    62  	byteSemaphore *kbfssync.Semaphore
    63  	fileLimit     int64
    64  	fileSemaphore *kbfssync.Semaphore
    65  	quotaTracker  *quotaSimpleTracker
    66  }
    67  
    68  var _ DiskLimiter = semaphoreDiskLimiter{}
    69  
    70  func newSemaphoreDiskLimiter(
    71  	byteLimit, fileLimit, quotaLimit int64) semaphoreDiskLimiter {
    72  	byteSemaphore := kbfssync.NewSemaphore()
    73  	byteSemaphore.Release(byteLimit)
    74  	fileSemaphore := kbfssync.NewSemaphore()
    75  	fileSemaphore.Release(fileLimit)
    76  	return semaphoreDiskLimiter{
    77  		byteLimit, byteSemaphore, fileLimit, fileSemaphore,
    78  		&quotaSimpleTracker{
    79  			quotaBytes: quotaLimit,
    80  		},
    81  	}
    82  }
    83  
    84  func (sdl semaphoreDiskLimiter) onJournalEnable(
    85  	ctx context.Context,
    86  	journalStoredBytes, journalUnflushedBytes, journalFiles int64,
    87  	_ keybase1.UserOrTeamID) (
    88  	availableBytes, availableFiles int64) {
    89  	if journalStoredBytes != 0 {
    90  		availableBytes = sdl.byteSemaphore.ForceAcquire(journalStoredBytes)
    91  	} else {
    92  		availableBytes = sdl.byteSemaphore.Count()
    93  	}
    94  	// storedBytes should be >= unflushedBytes. But it's not too
    95  	// bad to let it go through.
    96  	if journalFiles != 0 {
    97  		availableFiles = sdl.fileSemaphore.ForceAcquire(journalFiles)
    98  	} else {
    99  		availableFiles = sdl.fileSemaphore.Count()
   100  	}
   101  	sdl.quotaTracker.onJournalEnable(journalUnflushedBytes)
   102  	return availableBytes, availableFiles
   103  }
   104  
   105  func (sdl semaphoreDiskLimiter) onJournalDisable(
   106  	ctx context.Context,
   107  	journalStoredBytes, journalUnflushedBytes, journalFiles int64,
   108  	_ keybase1.UserOrTeamID) {
   109  	if journalStoredBytes != 0 {
   110  		sdl.byteSemaphore.Release(journalStoredBytes)
   111  	}
   112  	// As above, storedBytes should be >= unflushedBytes. Let it
   113  	// go through here, too.
   114  	if journalFiles != 0 {
   115  		sdl.fileSemaphore.Release(journalFiles)
   116  	}
   117  	sdl.quotaTracker.onJournalDisable(journalUnflushedBytes)
   118  }
   119  
   120  func (sdl semaphoreDiskLimiter) onSimpleByteTrackerEnable(
   121  	ctx context.Context, typ diskLimitTrackerType, diskCacheBytes int64) {
   122  	if diskCacheBytes != 0 {
   123  		sdl.byteSemaphore.ForceAcquire(diskCacheBytes)
   124  	}
   125  }
   126  
   127  func (sdl semaphoreDiskLimiter) onSimpleByteTrackerDisable(
   128  	ctx context.Context, typ diskLimitTrackerType, diskCacheBytes int64) {
   129  	if diskCacheBytes != 0 {
   130  		sdl.byteSemaphore.Release(diskCacheBytes)
   131  	}
   132  }
   133  
   134  func (sdl semaphoreDiskLimiter) reserveWithBackpressure(
   135  	ctx context.Context, typ diskLimitTrackerType, blockBytes, blockFiles int64,
   136  	_ keybase1.UserOrTeamID) (availableBytes, availableFiles int64, err error) {
   137  	// Better to return an error than to panic in Acquire.
   138  	if blockBytes == 0 {
   139  		return sdl.byteSemaphore.Count(), sdl.fileSemaphore.Count(), errors.New(
   140  			"semaphore.DiskLimiter.beforeBlockPut called with 0 blockBytes")
   141  	}
   142  	if blockFiles == 0 {
   143  		return sdl.byteSemaphore.Count(), sdl.fileSemaphore.Count(), errors.New(
   144  			"semaphore.DiskLimiter.beforeBlockPut called with 0 blockFiles")
   145  	}
   146  
   147  	availableBytes, err = sdl.byteSemaphore.Acquire(ctx, blockBytes)
   148  	if err != nil {
   149  		return availableBytes, sdl.fileSemaphore.Count(), err
   150  	}
   151  	defer func() {
   152  		if err != nil {
   153  			sdl.byteSemaphore.Release(blockBytes)
   154  			availableBytes = sdl.byteSemaphore.Count()
   155  		}
   156  	}()
   157  
   158  	availableFiles, err = sdl.fileSemaphore.Acquire(ctx, blockFiles)
   159  	return availableBytes, availableFiles, err
   160  }
   161  
   162  func (sdl semaphoreDiskLimiter) commitOrRollback(ctx context.Context,
   163  	typ diskLimitTrackerType, blockBytes, blockFiles int64, putData bool,
   164  	_ keybase1.UserOrTeamID) {
   165  	if !putData {
   166  		sdl.byteSemaphore.Release(blockBytes)
   167  		sdl.fileSemaphore.Release(blockFiles)
   168  	}
   169  	sdl.quotaTracker.afterBlockPut(blockBytes, putData)
   170  }
   171  
   172  func (sdl semaphoreDiskLimiter) onBlocksFlush(
   173  	ctx context.Context, blockBytes int64, _ keybase1.UserOrTeamID) {
   174  	sdl.quotaTracker.onBlocksFlush(blockBytes)
   175  }
   176  
   177  func (sdl semaphoreDiskLimiter) release(ctx context.Context,
   178  	typ diskLimitTrackerType, blockBytes, blockFiles int64) {
   179  	if blockBytes != 0 {
   180  		sdl.byteSemaphore.Release(blockBytes)
   181  	}
   182  	if blockFiles != 0 {
   183  		sdl.fileSemaphore.Release(blockFiles)
   184  	}
   185  }
   186  
   187  func (sdl semaphoreDiskLimiter) reserveBytes(ctx context.Context,
   188  	typ diskLimitTrackerType, blockBytes int64) (availableBytes int64,
   189  	err error) {
   190  	if blockBytes == 0 {
   191  		return 0, errors.New("semaphoreDiskLimiter.beforeDiskBlockCachePut" +
   192  			" called with 0 blockBytes")
   193  	}
   194  	return sdl.byteSemaphore.ForceAcquire(blockBytes), nil
   195  }
   196  
   197  func (sdl semaphoreDiskLimiter) getQuotaInfo(_ keybase1.UserOrTeamID) (
   198  	usedQuotaBytes, quotaBytes int64) {
   199  	return sdl.quotaTracker.getQuotaInfo()
   200  }
   201  
   202  func (sdl semaphoreDiskLimiter) getDiskLimitInfo() (
   203  	usedBytes int64, limitBytes float64, usedFiles int64, limitFiles float64) {
   204  	return sdl.byteSemaphore.Count(), float64(sdl.byteLimit),
   205  		sdl.fileSemaphore.Count(), float64(sdl.fileLimit)
   206  }
   207  
   208  type semaphoreDiskLimiterStatus struct {
   209  	Type string
   210  
   211  	// Derived numbers.
   212  	ByteUsageFrac  float64
   213  	FileUsageFrac  float64
   214  	QuotaUsageFrac float64
   215  
   216  	// Raw numbers.
   217  	ByteLimit  int64
   218  	ByteFree   int64
   219  	FileLimit  int64
   220  	FileFree   int64
   221  	QuotaLimit int64
   222  	QuotaUsed  int64
   223  }
   224  
   225  func (sdl semaphoreDiskLimiter) getStatus(
   226  	_ context.Context, _ keybase1.UserOrTeamID) interface{} {
   227  	byteFree := sdl.byteSemaphore.Count()
   228  	fileFree := sdl.fileSemaphore.Count()
   229  	usedQuotaBytes, quotaBytes := sdl.quotaTracker.getQuotaInfo()
   230  
   231  	byteUsageFrac := 1 - float64(byteFree)/float64(sdl.byteLimit)
   232  	fileUsageFrac := 1 - float64(fileFree)/float64(sdl.fileLimit)
   233  	quotaUsageFrac := float64(usedQuotaBytes) / float64(quotaBytes)
   234  
   235  	return semaphoreDiskLimiterStatus{
   236  		Type: "SemaphoreDiskLimiter",
   237  
   238  		ByteUsageFrac:  byteUsageFrac,
   239  		FileUsageFrac:  fileUsageFrac,
   240  		QuotaUsageFrac: quotaUsageFrac,
   241  
   242  		ByteLimit: sdl.byteLimit,
   243  		ByteFree:  byteFree,
   244  
   245  		FileLimit: sdl.fileLimit,
   246  		FileFree:  fileFree,
   247  
   248  		QuotaLimit: quotaBytes,
   249  		QuotaUsed:  usedQuotaBytes,
   250  	}
   251  }