github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/storage/feed/lookup/lookup.go (about) 1 // Copyleft 2018 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton 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 susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY 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 susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 /* 18 Package lookup defines feed lookup algorithms and provides tools to place updates 19 so they can be found 20 */ 21 package lookup 22 23 const maxuint64 = ^uint64(0) 24 25 // LowestLevel establishes the frequency resolution of the lookup algorithm as a power of 2. 26 const LowestLevel uint8 = 0 // default is 0 (1 second) 27 28 // HighestLevel sets the lowest frequency the algorithm will operate at, as a power of 2. 29 // 25 -> 2^25 equals to roughly one year. 30 const HighestLevel = 25 // default is 25 (~1 year) 31 32 // DefaultLevel sets what level will be chosen to search when there is no hint 33 const DefaultLevel = HighestLevel 34 35 //Algorithm is the function signature of a lookup algorithm 36 type Algorithm func(now uint64, hint Epoch, read ReadFunc) (value interface{}, err error) 37 38 // Lookup finds the update with the highest timestamp that is smaller or equal than 'now' 39 // It takes a hint which should be the epoch where the last known update was 40 // If you don't know in what epoch the last update happened, simply submit lookup.NoClue 41 // read() will be called on each lookup attempt 42 // Returns an error only if read() returns an error 43 // Returns nil if an update was not found 44 var Lookup Algorithm = FluzCapacitorAlgorithm 45 46 // ReadFunc is a handler called by Lookup each time it attempts to find a value 47 // It should return <nil> if a value is not found 48 // It should return <nil> if a value is found, but its timestamp is higher than "now" 49 // It should only return an error in case the handler wants to stop the 50 // lookup process entirely. 51 type ReadFunc func(epoch Epoch, now uint64) (interface{}, error) 52 53 // NoClue is a hint that can be provided when the Lookup caller does not have 54 // a clue about where the last update may be 55 var NoClue = Epoch{} 56 57 // getBaseTime returns the epoch base time of the given 58 // time and level 59 func getBaseTime(t uint64, level uint8) uint64 { 60 return t & (maxuint64 << level) 61 } 62 63 // Hint creates a hint based only on the last known update time 64 func Hint(last uint64) Epoch { 65 return Epoch{ 66 Time: last, 67 Level: DefaultLevel, 68 } 69 } 70 71 // GetNextLevel returns the frequency level a next update should be placed at, provided where 72 // the last update was and what time it is now. 73 // This is the first nonzero bit of the XOR of 'last' and 'now', counting from the highest significant bit 74 // but limited to not return a level that is smaller than the last-1 75 func GetNextLevel(last Epoch, now uint64) uint8 { 76 // First XOR the last epoch base time with the current clock. 77 // This will set all the common most significant bits to zero. 78 mix := (last.Base() ^ now) 79 80 // Then, make sure we stop the below loop before one level below the current, by setting 81 // that level's bit to 1. 82 // If the next level is lower than the current one, it must be exactly level-1 and not lower. 83 mix |= (1 << (last.Level - 1)) 84 85 // if the last update was more than 2^highestLevel seconds ago, choose the highest level 86 if mix > (maxuint64 >> (64 - HighestLevel - 1)) { 87 return HighestLevel 88 } 89 90 // set up a mask to scan for nonzero bits, starting at the highest level 91 mask := uint64(1 << (HighestLevel)) 92 93 for i := uint8(HighestLevel); i > LowestLevel; i-- { 94 if mix&mask != 0 { // if we find a nonzero bit, this is the level the next update should be at. 95 return i 96 } 97 mask = mask >> 1 // move our bit one position to the right 98 } 99 return 0 100 } 101 102 // GetNextEpoch returns the epoch where the next update should be located 103 // according to where the previous update was 104 // and what time it is now. 105 func GetNextEpoch(last Epoch, now uint64) Epoch { 106 if last == NoClue { 107 return GetFirstEpoch(now) 108 } 109 level := GetNextLevel(last, now) 110 return Epoch{ 111 Level: level, 112 Time: now, 113 } 114 } 115 116 // GetFirstEpoch returns the epoch where the first update should be located 117 // based on what time it is now. 118 func GetFirstEpoch(now uint64) Epoch { 119 return Epoch{Level: HighestLevel, Time: now} 120 } 121 122 var worstHint = Epoch{Time: 0, Level: 63} 123 124 // FluzCapacitorAlgorithm works by narrowing the epoch search area if an update is found 125 // going back and forth in time 126 // First, it will attempt to find an update where it should be now if the hint was 127 // really the last update. If that lookup fails, then the last update must be either the hint itself 128 // or the epochs right below. If however, that lookup succeeds, then the update must be 129 // that one or within the epochs right below. 130 // see the guide for a more graphical representation 131 func FluzCapacitorAlgorithm(now uint64, hint Epoch, read ReadFunc) (value interface{}, err error) { 132 var lastFound interface{} 133 var epoch Epoch 134 if hint == NoClue { 135 hint = worstHint 136 } 137 138 t := now 139 140 for { 141 epoch = GetNextEpoch(hint, t) 142 value, err = read(epoch, now) 143 if err != nil { 144 return nil, err 145 } 146 if value != nil { 147 lastFound = value 148 if epoch.Level == LowestLevel || epoch.Equals(hint) { 149 return value, nil 150 } 151 hint = epoch 152 continue 153 } 154 if epoch.Base() == hint.Base() { 155 if lastFound != nil { 156 return lastFound, nil 157 } 158 // we have reached the hint itself 159 if hint == worstHint { 160 return nil, nil 161 } 162 // check it out 163 value, err = read(hint, now) 164 if err != nil { 165 return nil, err 166 } 167 if value != nil { 168 return value, nil 169 } 170 // bad hint. 171 epoch = hint 172 hint = worstHint 173 } 174 base := epoch.Base() 175 if base == 0 { 176 return nil, nil 177 } 178 t = base - 1 179 } 180 }