github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/dirty_bcache_test.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 data 6 7 import ( 8 "testing" 9 "time" 10 11 "github.com/keybase/client/go/kbfs/kbfsblock" 12 "github.com/keybase/client/go/kbfs/test/clocktest" 13 "github.com/keybase/client/go/kbfs/tlf" 14 "github.com/keybase/client/go/libkb" 15 "github.com/keybase/client/go/logger" 16 "github.com/stretchr/testify/require" 17 "golang.org/x/net/context" 18 ) 19 20 func testDirtyBcachePut( 21 ctx context.Context, t *testing.T, id kbfsblock.ID, 22 dirtyBcache DirtyBlockCache) { 23 block := NewFileBlock() 24 ptr := BlockPointer{ID: id} 25 branch := MasterBranch 26 27 // put the block 28 tlfID := tlf.FakeID(1, tlf.Private) 29 if err := dirtyBcache.Put(ctx, tlfID, ptr, branch, block); err != nil { 30 t.Errorf("Got error on Put for block %s: %v", id, err) 31 } 32 33 // make sure we can get it successfully 34 if block2, err := dirtyBcache.Get(ctx, tlfID, ptr, branch); err != nil { 35 t.Errorf("Got error on get for block %s: %v", id, err) 36 } else if block2 != block { 37 t.Errorf("Got back unexpected block: %v", block2) 38 } 39 40 // make sure its dirty status is right 41 if !dirtyBcache.IsDirty(tlfID, ptr, branch) { 42 t.Errorf("Block %s unexpectedly not dirty", id) 43 } 44 } 45 46 func testExpectedMissingDirty( 47 ctx context.Context, t *testing.T, id kbfsblock.ID, 48 dirtyBcache DirtyBlockCache) { 49 expectedErr := NoSuchBlockError{id} 50 ptr := BlockPointer{ID: id} 51 tlfID := tlf.FakeID(1, tlf.Private) 52 if _, err := dirtyBcache.Get(ctx, tlfID, ptr, MasterBranch); err == nil { 53 t.Errorf("No expected error on 1st get: %v", err) 54 } else if err != expectedErr { 55 t.Errorf("Got unexpected error on 1st get: %v", err) 56 } 57 } 58 59 func testDirtyBcacheShutdown( 60 t *testing.T, dirtyBcache *DirtyBlockCacheStandard) { 61 err := dirtyBcache.Shutdown() 62 require.NoError(t, err) 63 } 64 65 func TestDirtyBcachePut(t *testing.T) { 66 log := logger.NewTestLogger(t) 67 dirtyBcache := NewDirtyBlockCacheStandard( 68 &WallClock{}, log, libkb.NewVDebugLog(log), 5<<20, 10<<20, 5<<20) 69 defer testDirtyBcacheShutdown(t, dirtyBcache) 70 testDirtyBcachePut( 71 context.Background(), t, kbfsblock.FakeID(1), dirtyBcache) 72 } 73 74 func TestDirtyBcachePutDuplicate(t *testing.T) { 75 log := logger.NewTestLogger(t) 76 dirtyBcache := NewDirtyBlockCacheStandard( 77 &WallClock{}, log, libkb.NewVDebugLog(log), 5<<20, 10<<20, 5<<20) 78 defer testDirtyBcacheShutdown(t, dirtyBcache) 79 id1 := kbfsblock.FakeID(1) 80 81 // Dirty a specific reference nonce, and make sure the 82 // original is still not found. 83 newNonce := kbfsblock.RefNonce([8]byte{1, 0, 0, 0, 0, 0, 0, 0}) 84 newNonceBlock := NewFileBlock() 85 bp1 := BlockPointer{ID: id1} 86 bp2 := BlockPointer{ 87 ID: id1, 88 Context: kbfsblock.Context{RefNonce: newNonce}, 89 } 90 id := tlf.FakeID(1, tlf.Private) 91 ctx := context.Background() 92 err := dirtyBcache.Put(ctx, id, bp2, MasterBranch, newNonceBlock) 93 if err != nil { 94 t.Errorf("Unexpected error on PutDirty: %v", err) 95 } 96 97 cleanBranch := MasterBranch 98 testExpectedMissingDirty(ctx, t, id1, dirtyBcache) 99 if !dirtyBcache.IsDirty(id, bp2, cleanBranch) { 100 t.Errorf("New refnonce block is now unexpectedly clean") 101 } 102 103 // Then dirty a different branch, and make sure the 104 // original is still clean 105 newBranch := BranchName("dirtyBranch") 106 newBranchBlock := NewFileBlock() 107 err = dirtyBcache.Put(ctx, id, bp1, newBranch, newBranchBlock) 108 if err != nil { 109 t.Errorf("Unexpected error on PutDirty: %v", err) 110 } 111 112 // make sure the original dirty status is right 113 testExpectedMissingDirty(ctx, t, id1, dirtyBcache) 114 if !dirtyBcache.IsDirty(id, bp2, cleanBranch) { 115 t.Errorf("New refnonce block is now unexpectedly clean") 116 } 117 if !dirtyBcache.IsDirty(id, bp1, newBranch) { 118 t.Errorf("New branch block is now unexpectedly clean") 119 } 120 } 121 122 func TestDirtyBcacheDelete(t *testing.T) { 123 log := logger.NewTestLogger(t) 124 dirtyBcache := NewDirtyBlockCacheStandard( 125 &WallClock{}, log, libkb.NewVDebugLog(log), 5<<20, 10<<20, 5<<20) 126 defer testDirtyBcacheShutdown(t, dirtyBcache) 127 128 id1 := kbfsblock.FakeID(1) 129 ctx := context.Background() 130 testDirtyBcachePut(ctx, t, id1, dirtyBcache) 131 newBranch := BranchName("dirtyBranch") 132 newBranchBlock := NewFileBlock() 133 id := tlf.FakeID(1, tlf.Private) 134 err := dirtyBcache.Put( 135 ctx, id, BlockPointer{ID: id1}, newBranch, newBranchBlock) 136 if err != nil { 137 t.Errorf("Unexpected error on PutDirty: %v", err) 138 } 139 140 err = dirtyBcache.Delete(id, BlockPointer{ID: id1}, MasterBranch) 141 require.NoError(t, err) 142 testExpectedMissingDirty(ctx, t, id1, dirtyBcache) 143 if !dirtyBcache.IsDirty(id, BlockPointer{ID: id1}, newBranch) { 144 t.Errorf("New branch block is now unexpectedly clean") 145 } 146 } 147 148 func TestDirtyBcacheRequestPermission(t *testing.T) { 149 bufSize := int64(5) 150 log := logger.NewTestLogger(t) 151 dirtyBcache := NewDirtyBlockCacheStandard( 152 &WallClock{}, log, libkb.NewVDebugLog(log), bufSize, bufSize*2, bufSize) 153 defer testDirtyBcacheShutdown(t, dirtyBcache) 154 blockedChan := make(chan int64, 1) 155 dirtyBcache.blockedChanForTesting = blockedChan 156 ctx := context.Background() 157 158 // The first write should get immediate permission. 159 id := tlf.FakeID(1, tlf.Private) 160 c1, err := dirtyBcache.RequestPermissionToDirty(ctx, id, bufSize*2+1) 161 if err != nil { 162 t.Fatalf("Request permission error: %v", err) 163 } 164 <-c1 165 // Now the unsynced buffer is full 166 if !dirtyBcache.ShouldForceSync(id) { 167 t.Fatalf("Unsynced not full after a request") 168 } 169 // Not blocked 170 if blockedSize := <-blockedChan; blockedSize != -1 { 171 t.Fatalf("Wrong blocked size: %d", blockedSize) 172 } 173 174 // The next request should block 175 c2, err := dirtyBcache.RequestPermissionToDirty(ctx, id, bufSize) 176 if err != nil { 177 t.Fatalf("Request permission error: %v", err) 178 } 179 if blockedSize := <-blockedChan; blockedSize != bufSize { 180 t.Fatalf("Wrong blocked size: %d", blockedSize) 181 } 182 select { 183 case <-c2: 184 t.Fatalf("Request should be blocked") 185 default: 186 } 187 188 // A 0-byte request should never fail. 189 c3, err := dirtyBcache.RequestPermissionToDirty(ctx, id, 0) 190 if err != nil { 191 t.Fatalf("Request permission error: %v", err) 192 } 193 select { 194 case <-c3: 195 default: 196 t.Fatalf("A 0-byte request was blocked") 197 } 198 199 // Let's say the actual number of unsynced bytes for c1 was double 200 dirtyBcache.UpdateUnsyncedBytes(id, 4*bufSize+2, false) 201 // Now release the previous bytes 202 dirtyBcache.UpdateUnsyncedBytes(id, -(2*bufSize + 1), false) 203 204 // Request 2 should still be blocked. (This check isn't 205 // fool-proof, since it doesn't necessarily give time for the 206 // background thread to run.) 207 if !dirtyBcache.ShouldForceSync(id) { 208 t.Fatalf("Total not full before sync finishes") 209 } 210 select { 211 case <-c2: 212 t.Fatalf("Request should be blocked") 213 default: 214 } 215 216 dirtyBcache.UpdateSyncingBytes(id, 4*bufSize+2) 217 if blockedSize := <-blockedChan; blockedSize != -1 { 218 t.Fatalf("Wrong blocked size: %d", blockedSize) 219 } 220 <-c2 // c2 is now unblocked since the wait buffer has drained. 221 // We should still need to sync the waitBuf caused by c2. 222 if !dirtyBcache.ShouldForceSync(id) { 223 t.Fatalf("Buffers not full after c2 accepted") 224 } 225 226 // Finish syncing most of the blocks, but the c2 sync hasn't 227 // finished. 228 dirtyBcache.BlockSyncFinished(id, 2*bufSize+1) 229 dirtyBcache.BlockSyncFinished(id, bufSize) 230 dirtyBcache.BlockSyncFinished(id, bufSize+1) 231 dirtyBcache.SyncFinished(id, 4*bufSize+2) 232 // c2. 233 dirtyBcache.UpdateSyncingBytes(id, bufSize) 234 dirtyBcache.BlockSyncFinished(id, bufSize) 235 dirtyBcache.SyncFinished(id, bufSize) 236 } 237 238 func TestDirtyBcacheCalcBackpressure(t *testing.T) { 239 bufSize := int64(10) 240 clock, now := clocktest.NewTestClockAndTimeNow() 241 log := logger.NewTestLogger(t) 242 dirtyBcache := NewDirtyBlockCacheStandard( 243 clock, log, libkb.NewVDebugLog(log), bufSize, bufSize*2, bufSize) 244 defer testDirtyBcacheShutdown(t, dirtyBcache) 245 // no backpressure yet 246 bp := dirtyBcache.calcBackpressure(now, now.Add(11*time.Second)) 247 if bp != 0 { 248 t.Fatalf("Unexpected backpressure before unsyned bytes: %d", bp) 249 } 250 251 // still less 252 id := tlf.FakeID(1, tlf.Private) 253 dirtyBcache.UpdateUnsyncedBytes(id, 9, false) 254 bp = dirtyBcache.calcBackpressure(now, now.Add(11*time.Second)) 255 if bp != 0 { 256 t.Fatalf("Unexpected backpressure before unsyned bytes: %d", bp) 257 } 258 259 // Now make 11 unsynced bytes, or 10% of the overage 260 dirtyBcache.UpdateUnsyncedBytes(id, 2, false) 261 bp = dirtyBcache.calcBackpressure(now, now.Add(11*time.Second)) 262 if g, e := bp, 1*time.Second; g != e { 263 t.Fatalf("Got backpressure %s, expected %s", g, e) 264 } 265 266 // Now completely fill the buffer 267 dirtyBcache.UpdateUnsyncedBytes(id, 9, false) 268 bp = dirtyBcache.calcBackpressure(now, now.Add(11*time.Second)) 269 if g, e := bp, 10*time.Second; g != e { 270 t.Fatalf("Got backpressure %s, expected %s", g, e) 271 } 272 273 // Now advance the clock, we should see the same bp deadline 274 clock.Add(5 * time.Second) 275 bp = dirtyBcache.calcBackpressure(now, now.Add(11*time.Second)) 276 if g, e := bp, 5*time.Second; g != e { 277 t.Fatalf("Got backpressure %s, expected %s", g, e) 278 } 279 280 dirtyBcache.UpdateSyncingBytes(id, 20) 281 dirtyBcache.BlockSyncFinished(id, 20) 282 dirtyBcache.SyncFinished(id, 20) 283 } 284 285 func TestDirtyBcacheResetBufferCap(t *testing.T) { 286 bufSize := int64(5) 287 log := logger.NewTestLogger(t) 288 dirtyBcache := NewDirtyBlockCacheStandard( 289 &WallClock{}, log, libkb.NewVDebugLog(log), bufSize, bufSize*2, bufSize) 290 defer testDirtyBcacheShutdown(t, dirtyBcache) 291 dirtyBcache.resetBufferCapTime = 1 * time.Millisecond 292 blockedChan := make(chan int64, 1) 293 dirtyBcache.blockedChanForTesting = blockedChan 294 ctx := context.Background() 295 296 // The first write should get immediate permission. 297 id := tlf.FakeID(1, tlf.Private) 298 c1, err := dirtyBcache.RequestPermissionToDirty(ctx, id, bufSize*2+1) 299 if err != nil { 300 t.Fatalf("Request permission error: %v", err) 301 } 302 <-c1 303 // Now the unsynced buffer is full 304 if !dirtyBcache.ShouldForceSync(id) { 305 t.Fatalf("Unsynced not full after a request") 306 } 307 // Not blocked 308 if blockedSize := <-blockedChan; blockedSize != -1 { 309 t.Fatalf("Wrong blocked size: %d", blockedSize) 310 } 311 312 // Finish it 313 dirtyBcache.UpdateSyncingBytes(id, 2*bufSize+1) 314 dirtyBcache.BlockSyncFinished(id, 2*bufSize+1) 315 dirtyBcache.SyncFinished(id, 2*bufSize+1) 316 317 // Wait for the reset 318 if blockedSize := <-blockedChan; blockedSize != -1 { 319 t.Fatalf("Wrong blocked size: %d", blockedSize) 320 } 321 322 if curr := dirtyBcache.getSyncBufferCap(); curr != bufSize { 323 t.Fatalf("Sync buffer cap was not reset, now %d", curr) 324 } 325 }