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  }