| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | // Copyright 2018 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package localstore | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/swarm/shed" | 
					
						
							|  |  |  | 	"github.com/syndtr/goleveldb/leveldb" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	// gcTargetRatio defines the target number of items | 
					
						
							|  |  |  | 	// in garbage collection index that will not be removed | 
					
						
							|  |  |  | 	// on garbage collection. The target number of items | 
					
						
							|  |  |  | 	// is calculated by gcTarget function. This value must be | 
					
						
							|  |  |  | 	// in range (0,1]. For example, with 0.9 value, | 
					
						
							|  |  |  | 	// garbage collection will leave 90% of defined capacity | 
					
						
							|  |  |  | 	// in database after its run. This prevents frequent | 
					
						
							|  |  |  | 	// garbage collection runs. | 
					
						
							|  |  |  | 	gcTargetRatio = 0.9 | 
					
						
							|  |  |  | 	// gcBatchSize limits the number of chunks in a single | 
					
						
							|  |  |  | 	// leveldb batch on garbage collection. | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	gcBatchSize uint64 = 1000 | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // collectGarbageWorker is a long running function that waits for | 
					
						
							|  |  |  | // collectGarbageTrigger channel to signal a garbage collection | 
					
						
							|  |  |  | // run. GC run iterates on gcIndex and removes older items | 
					
						
							|  |  |  | // form retrieval and other indexes. | 
					
						
							|  |  |  | func (db *DB) collectGarbageWorker() { | 
					
						
							| 
									
										
										
										
											2019-02-22 23:19:09 +01:00
										 |  |  | 	defer close(db.collectGarbageWorkerDone) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-db.collectGarbageTrigger: | 
					
						
							|  |  |  | 			// run a single collect garbage run and | 
					
						
							|  |  |  | 			// if done is false, gcBatchSize is reached and | 
					
						
							|  |  |  | 			// another collect garbage run is needed | 
					
						
							|  |  |  | 			collectedCount, done, err := db.collectGarbage() | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				log.Error("localstore collect garbage", "err", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// check if another gc run is needed | 
					
						
							|  |  |  | 			if !done { | 
					
						
							|  |  |  | 				db.triggerGarbageCollection() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 23:19:09 +01:00
										 |  |  | 			if collectedCount > 0 && testHookCollectGarbage != nil { | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 				testHookCollectGarbage(collectedCount) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case <-db.close: | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // collectGarbage removes chunks from retrieval and other | 
					
						
							|  |  |  | // indexes if maximal number of chunks in database is reached. | 
					
						
							|  |  |  | // This function returns the number of removed chunks. If done | 
					
						
							|  |  |  | // is false, another call to this function is needed to collect | 
					
						
							|  |  |  | // the rest of the garbage as the batch size limit is reached. | 
					
						
							|  |  |  | // This function is called in collectGarbageWorker. | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | func (db *DB) collectGarbage() (collectedCount uint64, done bool, err error) { | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	batch := new(leveldb.Batch) | 
					
						
							|  |  |  | 	target := db.gcTarget() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	// protect database from changing idexes and gcSize | 
					
						
							|  |  |  | 	db.batchMu.Lock() | 
					
						
							|  |  |  | 	defer db.batchMu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	gcSize, err := db.gcSize.Get() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return 0, true, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	done = true | 
					
						
							|  |  |  | 	err = db.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { | 
					
						
							|  |  |  | 		if gcSize-collectedCount <= target { | 
					
						
							|  |  |  | 			return true, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// delete from retrieve, pull, gc | 
					
						
							|  |  |  | 		db.retrievalDataIndex.DeleteInBatch(batch, item) | 
					
						
							|  |  |  | 		db.retrievalAccessIndex.DeleteInBatch(batch, item) | 
					
						
							|  |  |  | 		db.pullIndex.DeleteInBatch(batch, item) | 
					
						
							|  |  |  | 		db.gcIndex.DeleteInBatch(batch, item) | 
					
						
							|  |  |  | 		collectedCount++ | 
					
						
							|  |  |  | 		if collectedCount >= gcBatchSize { | 
					
						
							|  |  |  | 			// bach size limit reached, | 
					
						
							|  |  |  | 			// another gc run is needed | 
					
						
							|  |  |  | 			done = false | 
					
						
							|  |  |  | 			return true, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	}, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return 0, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	db.gcSize.PutInBatch(batch, gcSize-collectedCount) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	err = db.shed.WriteBatch(batch) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return 0, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return collectedCount, done, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // gcTrigger retruns the absolute value for garbage collection | 
					
						
							|  |  |  | // target value, calculated from db.capacity and gcTargetRatio. | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | func (db *DB) gcTarget() (target uint64) { | 
					
						
							|  |  |  | 	return uint64(float64(db.capacity) * gcTargetRatio) | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // triggerGarbageCollection signals collectGarbageWorker | 
					
						
							|  |  |  | // to call collectGarbage. | 
					
						
							|  |  |  | func (db *DB) triggerGarbageCollection() { | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case db.collectGarbageTrigger <- struct{}{}: | 
					
						
							|  |  |  | 	case <-db.close: | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | // incGCSizeInBatch changes gcSize field value | 
					
						
							|  |  |  | // by change which can be negative. This function | 
					
						
							|  |  |  | // must be called under batchMu lock. | 
					
						
							|  |  |  | func (db *DB) incGCSizeInBatch(batch *leveldb.Batch, change int64) (err error) { | 
					
						
							|  |  |  | 	if change == 0 { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	gcSize, err := db.gcSize.Get() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-22 23:19:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	var new uint64 | 
					
						
							|  |  |  | 	if change > 0 { | 
					
						
							|  |  |  | 		new = gcSize + uint64(change) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// 'change' is an int64 and is negative | 
					
						
							|  |  |  | 		// a conversion is needed with correct sign | 
					
						
							|  |  |  | 		c := uint64(-change) | 
					
						
							|  |  |  | 		if c > gcSize { | 
					
						
							|  |  |  | 			// protect uint64 undeflow | 
					
						
							|  |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 		new = gcSize - c | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	db.gcSize.PutInBatch(batch, new) | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	// trigger garbage collection if we reached the capacity | 
					
						
							|  |  |  | 	if new >= db.capacity { | 
					
						
							|  |  |  | 		db.triggerGarbageCollection() | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2019-02-07 18:40:26 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // testHookCollectGarbage is a hook that can provide | 
					
						
							|  |  |  | // information when a garbage collection run is done | 
					
						
							|  |  |  | // and how many items it removed. | 
					
						
							| 
									
										
										
										
											2019-03-09 00:06:39 +01:00
										 |  |  | var testHookCollectGarbage func(collectedCount uint64) |