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 "aSimpleTracker{ 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 }