github.com/ethersphere/bee/v2@v2.2.0/pkg/puller/intervalstore/intervals.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 intervalstore provides a persistence layer 18 // for intervals relating to a peer. The store 19 // provides basic operation such as adding intervals to 20 // existing ones and persisting the results, as well as 21 // getting next interval for a peer. 22 package intervalstore 23 24 import ( 25 "bytes" 26 "fmt" 27 "math" 28 "strconv" 29 "sync" 30 ) 31 32 // Intervals store a list of intervals. Its purpose is to provide 33 // methods to add new intervals and retrieve missing intervals that 34 // need to be added. 35 // It may be used in synchronization of streaming data to persist 36 // retrieved data ranges between sessions. 37 type Intervals struct { 38 start uint64 39 ranges [][2]uint64 40 mu sync.RWMutex 41 } 42 43 // New creates a new instance of Intervals. 44 // Start argument limits the lower bound of intervals. 45 // No range below start bound will be added by Add method or 46 // returned by Next method. This limit may be used for 47 // tracking "live" synchronization, where the sync session 48 // starts from a specific value, and if "live" sync intervals 49 // need to be merged with historical ones, it can be safely done. 50 func NewIntervals(start uint64) *Intervals { 51 return &Intervals{ 52 start: start, 53 } 54 } 55 56 // Add adds a new range to intervals. Range start and end are values 57 // are both inclusive. 58 func (i *Intervals) Add(start, end uint64) { 59 i.mu.Lock() 60 defer i.mu.Unlock() 61 62 i.add(start, end) 63 } 64 65 func (i *Intervals) add(start, end uint64) { 66 if start < i.start { 67 start = i.start 68 } 69 if end < i.start { 70 return 71 } 72 endCheck := end + 1 73 if end == math.MaxUint64 { 74 endCheck = end 75 } 76 minStartJ := -1 77 maxEndJ := -1 78 j := 0 79 for ; j < len(i.ranges); j++ { 80 if minStartJ < 0 { 81 if (start <= i.ranges[j][0] && endCheck >= i.ranges[j][0]) || (start <= i.ranges[j][1]+1 && endCheck >= i.ranges[j][1]) { 82 if i.ranges[j][0] < start { 83 start = i.ranges[j][0] 84 } 85 minStartJ = j 86 } 87 } 88 if (start <= i.ranges[j][1] && endCheck >= i.ranges[j][1]) || (start <= i.ranges[j][0] && endCheck >= i.ranges[j][0]) { 89 if i.ranges[j][1] > end { 90 end = i.ranges[j][1] 91 } 92 maxEndJ = j 93 } 94 if end+1 <= i.ranges[j][0] { 95 break 96 } 97 } 98 if minStartJ < 0 && maxEndJ < 0 { 99 i.ranges = append(i.ranges[:j], append([][2]uint64{{start, end}}, i.ranges[j:]...)...) 100 return 101 } 102 if minStartJ >= 0 { 103 i.ranges[minStartJ][0] = start 104 } 105 if maxEndJ >= 0 { 106 i.ranges[maxEndJ][1] = end 107 } 108 if minStartJ >= 0 && maxEndJ >= 0 && minStartJ != maxEndJ { 109 i.ranges[maxEndJ][0] = start 110 i.ranges = append(i.ranges[:minStartJ], i.ranges[maxEndJ:]...) 111 } 112 } 113 114 // Merge adds all the intervals from the m Interval to current one. 115 func (i *Intervals) Merge(m *Intervals) { 116 m.mu.RLock() 117 defer m.mu.RUnlock() 118 i.mu.Lock() 119 defer i.mu.Unlock() 120 121 for _, r := range m.ranges { 122 i.add(r[0], r[1]) 123 } 124 } 125 126 // Next returns the first range interval that is not fulfilled. Returned 127 // start and end values are both inclusive, meaning that the whole range 128 // including start and end need to be added in order to fill the gap 129 // in intervals. 130 // Returned value for end is 0 if the next interval is after the whole 131 // range that is stored in Intervals. Zero end value represents no limit 132 // on the next interval length. 133 // Argument ceiling is the upper bound for the returned range. 134 // Returned empty boolean indicates if both start and end values have 135 // reached the ceiling value which means that the returned range is empty, 136 // not containing a single element. 137 func (i *Intervals) Next(ceiling uint64) (start, end uint64, empty bool) { 138 i.mu.RLock() 139 defer func() { 140 if ceiling > 0 { 141 var ceilingHitStart, ceilingHitEnd bool 142 if start > ceiling { 143 start = ceiling 144 ceilingHitStart = true 145 } 146 if end == 0 || end > ceiling { 147 end = ceiling 148 ceilingHitEnd = true 149 } 150 empty = ceilingHitStart && ceilingHitEnd 151 } 152 i.mu.RUnlock() 153 }() 154 155 l := len(i.ranges) 156 if l == 0 { 157 return i.start, 0, false 158 } 159 if i.ranges[0][0] != i.start { 160 return i.start, i.ranges[0][0] - 1, false 161 } 162 if l == 1 { 163 return i.ranges[0][1] + 1, 0, false 164 } 165 return i.ranges[0][1] + 1, i.ranges[1][0] - 1, false 166 } 167 168 // Last returns the value that is at the end of the last interval. 169 func (i *Intervals) Last() (end uint64) { 170 i.mu.RLock() 171 defer i.mu.RUnlock() 172 173 l := len(i.ranges) 174 if l == 0 { 175 return 0 176 } 177 return i.ranges[l-1][1] 178 } 179 180 // String returns a descriptive representation of range intervals 181 // in [] notation, as a list of two element vectors. 182 func (i *Intervals) String() string { 183 return fmt.Sprint(i.ranges) 184 } 185 186 // MarshalBinary encodes Intervals parameters into a semicolon separated list. 187 // The first element in the list is base36-encoded start value. The following 188 // elements are two base36-encoded value ranges separated by comma. 189 func (i *Intervals) MarshalBinary() (data []byte, err error) { 190 d := make([][]byte, len(i.ranges)+1) 191 d[0] = []byte(strconv.FormatUint(i.start, 36)) 192 for j := range i.ranges { 193 r := i.ranges[j] 194 d[j+1] = []byte(strconv.FormatUint(r[0], 36) + "," + strconv.FormatUint(r[1], 36)) 195 } 196 return bytes.Join(d, []byte(";")), nil 197 } 198 199 // UnmarshalBinary decodes data according to the Intervals.MarshalBinary format. 200 func (i *Intervals) UnmarshalBinary(data []byte) (err error) { 201 d := bytes.Split(data, []byte(";")) 202 l := len(d) 203 if l == 0 { 204 return nil 205 } 206 if l >= 1 { 207 i.start, err = strconv.ParseUint(string(d[0]), 36, 64) 208 if err != nil { 209 return err 210 } 211 } 212 if l == 1 { 213 return nil 214 } 215 216 i.ranges = make([][2]uint64, 0, l-1) 217 for j := 1; j < l; j++ { 218 r := bytes.SplitN(d[j], []byte(","), 2) 219 if len(r) < 2 { 220 return fmt.Errorf("range %d has less then 2 elements", j) 221 } 222 start, err := strconv.ParseUint(string(r[0]), 36, 64) 223 if err != nil { 224 return fmt.Errorf("parsing the first element in range %d: %w", j, err) 225 } 226 end, err := strconv.ParseUint(string(r[1]), 36, 64) 227 if err != nil { 228 return fmt.Errorf("parsing the second element in range %d: %w", j, err) 229 } 230 i.add(start, end) 231 } 232 233 return nil 234 }