github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/storage/localstore/gc_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package localstore
    18  
    19  import (
    20  	"io/ioutil"
    21  	"math/rand"
    22  	"os"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/swarm/chunk"
    27  )
    28  
    29  // TestDB_collectGarbageWorker tests garbage collection runs
    30  // by uploading and syncing a number of chunks.
    31  func TestDB_collectGarbageWorker(t *testing.T) {
    32  	testDB_collectGarbageWorker(t)
    33  }
    34  
    35  // TestDB_collectGarbageWorker_multipleBatches tests garbage
    36  // collection runs by uploading and syncing a number of
    37  // chunks by having multiple smaller batches.
    38  func TestDB_collectGarbageWorker_multipleBatches(t *testing.T) {
    39  	// lower the maximal number of chunks in a single
    40  	// gc batch to ensure multiple batches.
    41  	defer func(s int64) { gcBatchSize = s }(gcBatchSize)
    42  	gcBatchSize = 2
    43  
    44  	testDB_collectGarbageWorker(t)
    45  }
    46  
    47  // testDB_collectGarbageWorker is a helper test function to test
    48  // garbage collection runs by uploading and syncing a number of chunks.
    49  func testDB_collectGarbageWorker(t *testing.T) {
    50  	t.Helper()
    51  
    52  	chunkCount := 150
    53  
    54  	db, cleanupFunc := newTestDB(t, &Options{
    55  		Capacity: 100,
    56  	})
    57  	testHookCollectGarbageChan := make(chan int64)
    58  	defer setTestHookCollectGarbage(func(collectedCount int64) {
    59  		select {
    60  		case testHookCollectGarbageChan <- collectedCount:
    61  		case <-db.close:
    62  		}
    63  	})()
    64  	defer cleanupFunc()
    65  
    66  	uploader := db.NewPutter(ModePutUpload)
    67  	syncer := db.NewSetter(ModeSetSync)
    68  
    69  	addrs := make([]chunk.Address, 0)
    70  
    71  	// upload random chunks
    72  	for i := 0; i < chunkCount; i++ {
    73  		chunk := generateTestRandomChunk()
    74  
    75  		err := uploader.Put(chunk)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  
    80  		err = syncer.Set(chunk.Address())
    81  		if err != nil {
    82  			t.Fatal(err)
    83  		}
    84  
    85  		addrs = append(addrs, chunk.Address())
    86  	}
    87  
    88  	gcTarget := db.gcTarget()
    89  
    90  	for {
    91  		select {
    92  		case <-testHookCollectGarbageChan:
    93  		case <-time.After(10 * time.Second):
    94  			t.Error("collect garbage timeout")
    95  		}
    96  		gcSize := db.getGCSize()
    97  		if gcSize == gcTarget {
    98  			break
    99  		}
   100  	}
   101  
   102  	t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget)))
   103  
   104  	t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget)))
   105  
   106  	t.Run("gc size", newIndexGCSizeTest(db))
   107  
   108  	// the first synced chunk should be removed
   109  	t.Run("get the first synced chunk", func(t *testing.T) {
   110  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   111  		if err != chunk.ErrChunkNotFound {
   112  			t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
   113  		}
   114  	})
   115  
   116  	// last synced chunk should not be removed
   117  	t.Run("get most recent synced chunk", func(t *testing.T) {
   118  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1])
   119  		if err != nil {
   120  			t.Fatal(err)
   121  		}
   122  	})
   123  }
   124  
   125  // TestDB_collectGarbageWorker_withRequests is a helper test function
   126  // to test garbage collection runs by uploading, syncing and
   127  // requesting a number of chunks.
   128  func TestDB_collectGarbageWorker_withRequests(t *testing.T) {
   129  	db, cleanupFunc := newTestDB(t, &Options{
   130  		Capacity: 100,
   131  	})
   132  	defer cleanupFunc()
   133  
   134  	uploader := db.NewPutter(ModePutUpload)
   135  	syncer := db.NewSetter(ModeSetSync)
   136  
   137  	testHookCollectGarbageChan := make(chan int64)
   138  	defer setTestHookCollectGarbage(func(collectedCount int64) {
   139  		testHookCollectGarbageChan <- collectedCount
   140  	})()
   141  
   142  	addrs := make([]chunk.Address, 0)
   143  
   144  	// upload random chunks just up to the capacity
   145  	for i := 0; i < int(db.capacity)-1; i++ {
   146  		chunk := generateTestRandomChunk()
   147  
   148  		err := uploader.Put(chunk)
   149  		if err != nil {
   150  			t.Fatal(err)
   151  		}
   152  
   153  		err = syncer.Set(chunk.Address())
   154  		if err != nil {
   155  			t.Fatal(err)
   156  		}
   157  
   158  		addrs = append(addrs, chunk.Address())
   159  	}
   160  
   161  	// set update gc test hook to signal when
   162  	// update gc goroutine is done by closing
   163  	// testHookUpdateGCChan channel
   164  	testHookUpdateGCChan := make(chan struct{})
   165  	resetTestHookUpdateGC := setTestHookUpdateGC(func() {
   166  		close(testHookUpdateGCChan)
   167  	})
   168  
   169  	// request the latest synced chunk
   170  	// to prioritize it in the gc index
   171  	// not to be collected
   172  	_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  
   177  	// wait for update gc goroutine to finish for garbage
   178  	// collector to be correctly triggered after the last upload
   179  	select {
   180  	case <-testHookUpdateGCChan:
   181  	case <-time.After(10 * time.Second):
   182  		t.Fatal("updateGC was not called after getting chunk with ModeGetRequest")
   183  	}
   184  
   185  	// no need to wait for update gc hook anymore
   186  	resetTestHookUpdateGC()
   187  
   188  	// upload and sync another chunk to trigger
   189  	// garbage collection
   190  	ch := generateTestRandomChunk()
   191  	err = uploader.Put(ch)
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	err = syncer.Set(ch.Address())
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	addrs = append(addrs, ch.Address())
   200  
   201  	// wait for garbage collection
   202  
   203  	gcTarget := db.gcTarget()
   204  
   205  	var totalCollectedCount int64
   206  	for {
   207  		select {
   208  		case c := <-testHookCollectGarbageChan:
   209  			totalCollectedCount += c
   210  		case <-time.After(10 * time.Second):
   211  			t.Error("collect garbage timeout")
   212  		}
   213  		gcSize := db.getGCSize()
   214  		if gcSize == gcTarget {
   215  			break
   216  		}
   217  	}
   218  
   219  	wantTotalCollectedCount := int64(len(addrs)) - gcTarget
   220  	if totalCollectedCount != wantTotalCollectedCount {
   221  		t.Errorf("total collected chunks %v, want %v", totalCollectedCount, wantTotalCollectedCount)
   222  	}
   223  
   224  	t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget)))
   225  
   226  	t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget)))
   227  
   228  	t.Run("gc size", newIndexGCSizeTest(db))
   229  
   230  	// requested chunk should not be removed
   231  	t.Run("get requested chunk", func(t *testing.T) {
   232  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[0])
   233  		if err != nil {
   234  			t.Fatal(err)
   235  		}
   236  	})
   237  
   238  	// the second synced chunk should be removed
   239  	t.Run("get gc-ed chunk", func(t *testing.T) {
   240  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[1])
   241  		if err != chunk.ErrChunkNotFound {
   242  			t.Errorf("got error %v, want %v", err, chunk.ErrChunkNotFound)
   243  		}
   244  	})
   245  
   246  	// last synced chunk should not be removed
   247  	t.Run("get most recent synced chunk", func(t *testing.T) {
   248  		_, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1])
   249  		if err != nil {
   250  			t.Fatal(err)
   251  		}
   252  	})
   253  }
   254  
   255  // TestDB_gcSize checks if gcSize has a correct value after
   256  // database is initialized with existing data.
   257  func TestDB_gcSize(t *testing.T) {
   258  	dir, err := ioutil.TempDir("", "localstore-stored-gc-size")
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	defer os.RemoveAll(dir)
   263  	baseKey := make([]byte, 32)
   264  	if _, err := rand.Read(baseKey); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  	db, err := New(dir, baseKey, nil)
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  
   272  	uploader := db.NewPutter(ModePutUpload)
   273  	syncer := db.NewSetter(ModeSetSync)
   274  
   275  	count := 100
   276  
   277  	for i := 0; i < count; i++ {
   278  		chunk := generateTestRandomChunk()
   279  
   280  		err := uploader.Put(chunk)
   281  		if err != nil {
   282  			t.Fatal(err)
   283  		}
   284  
   285  		err = syncer.Set(chunk.Address())
   286  		if err != nil {
   287  			t.Fatal(err)
   288  		}
   289  	}
   290  
   291  	// DB.Close writes gc size to disk, so
   292  	// Instead calling Close, simulate database shutdown
   293  	// without it.
   294  	close(db.close)
   295  	db.updateGCWG.Wait()
   296  	err = db.shed.Close()
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  
   301  	db, err = New(dir, baseKey, nil)
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  	defer db.Close()
   306  
   307  	t.Run("gc index size", newIndexGCSizeTest(db))
   308  
   309  	t.Run("gc uncounted hashes index count", newItemsCountTest(db.gcUncountedHashesIndex, 0))
   310  }
   311  
   312  // setTestHookCollectGarbage sets testHookCollectGarbage and
   313  // returns a function that will reset it to the
   314  // value before the change.
   315  func setTestHookCollectGarbage(h func(collectedCount int64)) (reset func()) {
   316  	current := testHookCollectGarbage
   317  	reset = func() { testHookCollectGarbage = current }
   318  	testHookCollectGarbage = h
   319  	return reset
   320  }
   321  
   322  // TestSetTestHookCollectGarbage tests if setTestHookCollectGarbage changes
   323  // testHookCollectGarbage function correctly and if its reset function
   324  // resets the original function.
   325  func TestSetTestHookCollectGarbage(t *testing.T) {
   326  	// Set the current function after the test finishes.
   327  	defer func(h func(collectedCount int64)) { testHookCollectGarbage = h }(testHookCollectGarbage)
   328  
   329  	// expected value for the unchanged function
   330  	original := 1
   331  	// expected value for the changed function
   332  	changed := 2
   333  
   334  	// this variable will be set with two different functions
   335  	var got int
   336  
   337  	// define the original (unchanged) functions
   338  	testHookCollectGarbage = func(_ int64) {
   339  		got = original
   340  	}
   341  
   342  	// set got variable
   343  	testHookCollectGarbage(0)
   344  
   345  	// test if got variable is set correctly
   346  	if got != original {
   347  		t.Errorf("got hook value %v, want %v", got, original)
   348  	}
   349  
   350  	// set the new function
   351  	reset := setTestHookCollectGarbage(func(_ int64) {
   352  		got = changed
   353  	})
   354  
   355  	// set got variable
   356  	testHookCollectGarbage(0)
   357  
   358  	// test if got variable is set correctly to changed value
   359  	if got != changed {
   360  		t.Errorf("got hook value %v, want %v", got, changed)
   361  	}
   362  
   363  	// set the function to the original one
   364  	reset()
   365  
   366  	// set got variable
   367  	testHookCollectGarbage(0)
   368  
   369  	// test if got variable is set correctly to original value
   370  	if got != original {
   371  		t.Errorf("got hook value %v, want %v", got, original)
   372  	}
   373  }