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 }