github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/block_retrieval_queue_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 libkbfs
     6  
     7  import (
     8  	"io"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/eapache/channels"
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/keybase/client/go/kbfs/data"
    15  	"github.com/keybase/client/go/kbfs/env"
    16  	"github.com/keybase/client/go/kbfs/kbfsblock"
    17  	"github.com/keybase/client/go/kbfs/libkey"
    18  	libkeytest "github.com/keybase/client/go/kbfs/libkey/test"
    19  	"github.com/keybase/client/go/kbfs/test/clocktest"
    20  	"github.com/keybase/client/go/kbfs/tlf"
    21  	"github.com/keybase/client/go/libkb"
    22  	"github.com/keybase/client/go/protocol/keybase1"
    23  	"github.com/stretchr/testify/require"
    24  	"golang.org/x/net/context"
    25  )
    26  
    27  type testBlockRetrievalConfig struct {
    28  	codecGetter
    29  	logMaker
    30  	testCache data.BlockCache
    31  	bg        blockGetter
    32  	bs        BlockServer
    33  	*testDiskBlockCacheGetter
    34  	*testSyncedTlfGetterSetter
    35  	initModeGetter
    36  	clock                        Clock
    37  	reporter                     Reporter
    38  	subscriptionManager          SubscriptionManager
    39  	subscriptionManagerPublisher SubscriptionManagerPublisher
    40  }
    41  
    42  func newTestBlockRetrievalConfig(t *testing.T, bg blockGetter,
    43  	dbc DiskBlockCache) *testBlockRetrievalConfig {
    44  	clock := clocktest.NewTestClockNow()
    45  	ctlr := gomock.NewController(t)
    46  	mockPublisher := NewMockSubscriptionManagerPublisher(ctlr)
    47  	mockPublisher.EXPECT().PublishChange(gomock.Any()).AnyTimes()
    48  	return &testBlockRetrievalConfig{
    49  		newTestCodecGetter(),
    50  		newTestLogMakerWithVDebug(t, libkb.VLog2String),
    51  		data.NewBlockCacheStandard(10, getDefaultCleanBlockCacheCapacity(NewInitModeFromType(InitDefault))),
    52  		bg,
    53  		NewMockBlockServer(ctlr),
    54  		newTestDiskBlockCacheGetter(t, dbc),
    55  		newTestSyncedTlfGetterSetter(),
    56  		testInitModeGetter{InitDefault},
    57  		clock,
    58  		NewReporterSimple(clock, 1),
    59  		nil,
    60  		mockPublisher,
    61  	}
    62  }
    63  
    64  func (c *testBlockRetrievalConfig) BlockCache() data.BlockCache {
    65  	return c.testCache
    66  }
    67  
    68  func (c testBlockRetrievalConfig) DataVersion() data.Ver {
    69  	return data.ChildHolesVer
    70  }
    71  
    72  func (c testBlockRetrievalConfig) Clock() Clock {
    73  	return c.clock
    74  }
    75  
    76  func (c testBlockRetrievalConfig) Reporter() Reporter {
    77  	return c.reporter
    78  }
    79  
    80  func (c testBlockRetrievalConfig) blockGetter() blockGetter {
    81  	return c.bg
    82  }
    83  
    84  func (c testBlockRetrievalConfig) BlockServer() BlockServer {
    85  	return c.bs
    86  }
    87  
    88  func (c testBlockRetrievalConfig) GetSettingsDB() *SettingsDB {
    89  	return nil
    90  }
    91  
    92  func (c testBlockRetrievalConfig) SubscriptionManager(
    93  	_ SubscriptionManagerClientID, _ bool,
    94  	_ SubscriptionNotifier) SubscriptionManager {
    95  	return c.subscriptionManager
    96  }
    97  
    98  func (c testBlockRetrievalConfig) SubscriptionManagerPublisher() SubscriptionManagerPublisher {
    99  	return c.subscriptionManagerPublisher
   100  }
   101  
   102  func makeRandomBlockPointer(t *testing.T) data.BlockPointer {
   103  	id, err := kbfsblock.MakeFakeID()
   104  	require.NoError(t, err)
   105  	return data.BlockPointer{
   106  		ID:         id,
   107  		KeyGen:     5,
   108  		DataVer:    1,
   109  		DirectType: data.DirectBlock,
   110  		Context: kbfsblock.MakeContext(
   111  			"fake creator",
   112  			"fake writer",
   113  			kbfsblock.RefNonce{0xb},
   114  			keybase1.BlockType_DATA,
   115  		),
   116  	}
   117  }
   118  
   119  func makeKMD() libkey.KeyMetadata {
   120  	return libkeytest.NewEmptyKeyMetadata(tlf.FakeID(0, tlf.Private), 1)
   121  }
   122  
   123  func initBlockRetrievalQueueTest(t *testing.T) *blockRetrievalQueue {
   124  	q := newBlockRetrievalQueue(
   125  		0, 0, 0, newTestBlockRetrievalConfig(t, nil, nil),
   126  		env.EmptyAppStateUpdater{})
   127  	<-q.TogglePrefetcher(false, nil, nil)
   128  	return q
   129  }
   130  
   131  func endBlockRetrievalQueueTest(t *testing.T, q *blockRetrievalQueue) {
   132  	t.Helper()
   133  	select {
   134  	case <-q.Shutdown():
   135  	case <-time.After(5 * time.Second):
   136  		t.Fatal("Waited too long for block retrieval queue to shutdown")
   137  	}
   138  }
   139  
   140  func TestBlockRetrievalQueueBasic(t *testing.T) {
   141  	t.Log("Add a block retrieval request to the queue and retrieve it.")
   142  	q := initBlockRetrievalQueueTest(t)
   143  	require.NotNil(t, q)
   144  	defer endBlockRetrievalQueueTest(t, q)
   145  
   146  	ctx := context.Background()
   147  	ptr1 := makeRandomBlockPointer(t)
   148  	block := &data.FileBlock{}
   149  	t.Log("Request a block retrieval for ptr1.")
   150  	_ = q.Request(
   151  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   152  		data.NoCacheEntry, BlockRequestWithPrefetch)
   153  
   154  	t.Log("Begin working on the request.")
   155  	br := q.popIfNotEmpty()
   156  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   157  	require.Equal(t, ptr1, br.blockPtr)
   158  	require.Equal(t, -1, br.index)
   159  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   160  	require.Equal(t, uint64(0), br.insertionOrder)
   161  	require.Len(t, br.requests, 1)
   162  	require.Equal(t, block, br.requests[0].block)
   163  }
   164  
   165  func TestBlockRetrievalQueuePreemptPriority(t *testing.T) {
   166  	t.Log("Preempt a lower-priority block retrieval request with a higher " +
   167  		"priority request.")
   168  	q := initBlockRetrievalQueueTest(t)
   169  	require.NotNil(t, q)
   170  	defer endBlockRetrievalQueueTest(t, q)
   171  
   172  	ctx := context.Background()
   173  	ptr1 := makeRandomBlockPointer(t)
   174  	ptr2 := makeRandomBlockPointer(t)
   175  	block := &data.FileBlock{}
   176  	t.Log("Request a block retrieval for ptr1 and a higher priority " +
   177  		"retrieval for ptr2.")
   178  	_ = q.Request(
   179  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   180  		data.NoCacheEntry, BlockRequestWithPrefetch)
   181  	_ = q.Request(ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr2,
   182  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   183  
   184  	t.Log("Begin working on the preempted ptr2 request.")
   185  	br := q.popIfNotEmpty()
   186  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   187  	require.Equal(t, ptr2, br.blockPtr)
   188  	require.Equal(t, defaultOnDemandRequestPriority+1, br.priority)
   189  	require.Equal(t, uint64(1), br.insertionOrder)
   190  
   191  	t.Log("Begin working on the ptr1 request.")
   192  	br = q.popIfNotEmpty()
   193  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   194  	require.Equal(t, ptr1, br.blockPtr)
   195  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   196  	require.Equal(t, uint64(0), br.insertionOrder)
   197  }
   198  
   199  func TestBlockRetrievalQueueInterleavedPreemption(t *testing.T) {
   200  	t.Log("Handle a first request and then preempt another one.")
   201  	q := initBlockRetrievalQueueTest(t)
   202  	require.NotNil(t, q)
   203  	defer endBlockRetrievalQueueTest(t, q)
   204  
   205  	ctx := context.Background()
   206  	ptr1 := makeRandomBlockPointer(t)
   207  	ptr2 := makeRandomBlockPointer(t)
   208  	block := &data.FileBlock{}
   209  	t.Log("Request a block retrieval for ptr1 and ptr2.")
   210  	_ = q.Request(
   211  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   212  		data.NoCacheEntry, BlockRequestWithPrefetch)
   213  	_ = q.Request(
   214  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr2, block,
   215  		data.NoCacheEntry, BlockRequestWithPrefetch)
   216  
   217  	t.Log("Begin working on the ptr1 request.")
   218  	br := q.popIfNotEmpty()
   219  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   220  	require.Equal(t, ptr1, br.blockPtr)
   221  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   222  	require.Equal(t, uint64(0), br.insertionOrder)
   223  
   224  	ptr3 := makeRandomBlockPointer(t)
   225  	t.Log("Preempt the ptr2 request with the ptr3 request.")
   226  	_ = q.Request(
   227  		ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr3,
   228  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   229  
   230  	t.Log("Begin working on the ptr3 request.")
   231  	br = q.popIfNotEmpty()
   232  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   233  	require.Equal(t, ptr3, br.blockPtr)
   234  	require.Equal(t, defaultOnDemandRequestPriority+1, br.priority)
   235  	require.Equal(t, uint64(2), br.insertionOrder)
   236  
   237  	t.Log("Begin working on the ptr2 request.")
   238  	br = q.popIfNotEmpty()
   239  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   240  	require.Equal(t, ptr2, br.blockPtr)
   241  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   242  	require.Equal(t, uint64(1), br.insertionOrder)
   243  }
   244  
   245  func TestBlockRetrievalQueueMultipleRequestsSameBlock(t *testing.T) {
   246  	t.Log("Request the same block multiple times.")
   247  	q := initBlockRetrievalQueueTest(t)
   248  	require.NotNil(t, q)
   249  	defer endBlockRetrievalQueueTest(t, q)
   250  
   251  	ctx := context.Background()
   252  	ptr1 := makeRandomBlockPointer(t)
   253  	block := &data.FileBlock{}
   254  	t.Log("Request a block retrieval for ptr1 twice.")
   255  	_ = q.Request(
   256  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   257  		data.NoCacheEntry, BlockRequestWithPrefetch)
   258  	_ = q.Request(
   259  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   260  		data.NoCacheEntry, BlockRequestWithPrefetch)
   261  
   262  	t.Log("Begin working on the ptr1 retrieval. Verify that it has 2 requests and that the queue is now empty.")
   263  	br := q.popIfNotEmpty()
   264  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   265  	require.Equal(t, ptr1, br.blockPtr)
   266  	require.Equal(t, -1, br.index)
   267  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   268  	require.Equal(t, uint64(0), br.insertionOrder)
   269  	require.Len(t, br.requests, 2)
   270  	require.Len(t, *q.heap, 0)
   271  	require.Equal(t, block, br.requests[0].block)
   272  	require.Equal(t, block, br.requests[1].block)
   273  }
   274  
   275  func TestBlockRetrievalQueueElevatePriorityExistingRequest(t *testing.T) {
   276  	t.Log("Elevate the priority on an existing request.")
   277  	q := initBlockRetrievalQueueTest(t)
   278  	require.NotNil(t, q)
   279  	defer endBlockRetrievalQueueTest(t, q)
   280  
   281  	ctx := context.Background()
   282  	ptr1 := makeRandomBlockPointer(t)
   283  	ptr2 := makeRandomBlockPointer(t)
   284  	ptr3 := makeRandomBlockPointer(t)
   285  	block := &data.FileBlock{}
   286  	t.Log("Request 3 block retrievals, each preempting the previous one.")
   287  	_ = q.Request(
   288  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   289  		data.NoCacheEntry, BlockRequestWithPrefetch)
   290  	_ = q.Request(
   291  		ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr2,
   292  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   293  	_ = q.Request(
   294  		ctx, defaultOnDemandRequestPriority+2, makeKMD(), ptr3,
   295  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   296  
   297  	t.Log("Begin working on the ptr3 retrieval.")
   298  	br := q.popIfNotEmpty()
   299  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   300  	require.Equal(t, ptr3, br.blockPtr)
   301  	require.Equal(t, defaultOnDemandRequestPriority+2, br.priority)
   302  	require.Equal(t, uint64(2), br.insertionOrder)
   303  
   304  	t.Log("Preempt the remaining retrievals with another retrieval for ptr1.")
   305  	_ = q.Request(
   306  		ctx, defaultOnDemandRequestPriority+2, makeKMD(), ptr1,
   307  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   308  
   309  	t.Log("Begin working on the ptr1 retrieval. Verify that it has increased in priority and has 2 requests.")
   310  	br = q.popIfNotEmpty()
   311  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   312  	require.Equal(t, ptr1, br.blockPtr)
   313  	require.Equal(t, defaultOnDemandRequestPriority+2, br.priority)
   314  	require.Equal(t, uint64(0), br.insertionOrder)
   315  	require.Len(t, br.requests, 2)
   316  
   317  	t.Log("Begin working on the ptr2 retrieval.")
   318  	br = q.popIfNotEmpty()
   319  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   320  	require.Equal(t, ptr2, br.blockPtr)
   321  	require.Equal(t, defaultOnDemandRequestPriority+1, br.priority)
   322  	require.Equal(t, uint64(1), br.insertionOrder)
   323  }
   324  
   325  func TestBlockRetrievalQueueCurrentlyProcessingRequest(t *testing.T) {
   326  	t.Log("Begin processing a request and then add another one for the same block.")
   327  	q := initBlockRetrievalQueueTest(t)
   328  	require.NotNil(t, q)
   329  	defer endBlockRetrievalQueueTest(t, q)
   330  
   331  	ctx := context.Background()
   332  	ptr1 := makeRandomBlockPointer(t)
   333  	block := &data.FileBlock{}
   334  	t.Log("Request a block retrieval for ptr1.")
   335  	_ = q.Request(
   336  		ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block,
   337  		data.NoCacheEntry, BlockRequestWithPrefetch)
   338  
   339  	t.Log("Begin working on the ptr1 retrieval. Verify that it has 1 request.")
   340  	br := q.popIfNotEmpty()
   341  	require.Equal(t, ptr1, br.blockPtr)
   342  	require.Equal(t, -1, br.index)
   343  	require.Equal(t, defaultOnDemandRequestPriority, br.priority)
   344  	require.Equal(t, uint64(0), br.insertionOrder)
   345  	require.Len(t, br.requests, 1)
   346  	require.Equal(t, block, br.requests[0].block)
   347  
   348  	t.Log("Request another block retrieval for ptr1 before it has finished. " +
   349  		"Verify that the priority has elevated and there are now 2 requests.")
   350  	_ = q.Request(
   351  		ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr1,
   352  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   353  	require.Equal(t, defaultOnDemandRequestPriority+1, br.priority)
   354  	require.Equal(t, uint64(0), br.insertionOrder)
   355  	require.Len(t, br.requests, 2)
   356  	require.Equal(t, block, br.requests[0].block)
   357  	require.Equal(t, block, br.requests[1].block)
   358  
   359  	t.Log("Finalize the existing request for ptr1.")
   360  	q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, nil)
   361  	t.Log("Make another request for the same block. Verify that this is a new request.")
   362  	_ = q.Request(
   363  		ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr1,
   364  		block, data.NoCacheEntry, BlockRequestWithPrefetch)
   365  	br = q.popIfNotEmpty()
   366  	defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF)
   367  	require.Equal(t, defaultOnDemandRequestPriority+1, br.priority)
   368  	require.Equal(t, uint64(1), br.insertionOrder)
   369  	require.Len(t, br.requests, 1)
   370  	require.Equal(t, block, br.requests[0].block)
   371  }
   372  
   373  func TestBlockRetrievalQueueThrottling(t *testing.T) {
   374  	t.Log("Start test with no throttling channel so we can pass in our own")
   375  	q := initBlockRetrievalQueueTest(t)
   376  	require.NotNil(t, q)
   377  	defer endBlockRetrievalQueueTest(t, q)
   378  
   379  	throttleCh := channels.NewInfiniteChannel()
   380  	q.throttledWorkCh = throttleCh
   381  
   382  	t.Log("Make a few throttled requests that won't be serviced until we " +
   383  		"start the background loop.")
   384  
   385  	ctx := context.Background()
   386  	ptr1 := makeRandomBlockPointer(t)
   387  	block1 := &data.FileBlock{}
   388  	_ = q.Request(
   389  		ctx, throttleRequestPriority, makeKMD(), ptr1, block1,
   390  		data.NoCacheEntry, BlockRequestSolo)
   391  	ptr2 := makeRandomBlockPointer(t)
   392  	block2 := &data.FileBlock{}
   393  	_ = q.Request(
   394  		ctx, throttleRequestPriority-100, makeKMD(), ptr2, block2,
   395  		data.NoCacheEntry, BlockRequestSolo)
   396  
   397  	t.Log("Make sure they are queued to be throttled")
   398  	require.Equal(t, 2, throttleCh.Len())
   399  
   400  	t.Log("Start background loop with short period")
   401  	go q.throttleReleaseLoop(1 * time.Millisecond)
   402  	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
   403  	defer cancel()
   404  	for throttleCh.Len() > 0 {
   405  		time.Sleep(1 * time.Millisecond)
   406  		select {
   407  		case <-ctx.Done():
   408  			t.Fatal(ctx.Err())
   409  		default:
   410  		}
   411  	}
   412  }