github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/swarm/storage/localstore/subscription_pull.go (about) 1 // Copyright 2019 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 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "sync" 25 26 "github.com/ethereum/go-ethereum/log" 27 "github.com/ethereum/go-ethereum/swarm/chunk" 28 "github.com/ethereum/go-ethereum/swarm/shed" 29 "github.com/syndtr/goleveldb/leveldb" 30 ) 31 32 // SubscribePull returns a channel that provides chunk addresses and stored times from pull syncing index. 33 // Pull syncing index can be only subscribed to a particular proximity order bin. If since 34 // is not nil, the iteration will start from the first item stored after that timestamp. If until is not nil, 35 // only chunks stored up to this timestamp will be send to the channel, and the returned channel will be 36 // closed. The since-until interval is open on the left and closed on the right (since,until]. Returned stop 37 // function will terminate current and further iterations without errors, and also close the returned channel. 38 // Make sure that you check the second returned parameter from the channel to stop iteration when its value 39 // is false. 40 func (db *DB) SubscribePull(ctx context.Context, bin uint8, since, until *ChunkDescriptor) (c <-chan ChunkDescriptor, stop func()) { 41 chunkDescriptors := make(chan ChunkDescriptor) 42 trigger := make(chan struct{}, 1) 43 44 db.pullTriggersMu.Lock() 45 if _, ok := db.pullTriggers[bin]; !ok { 46 db.pullTriggers[bin] = make([]chan struct{}, 0) 47 } 48 db.pullTriggers[bin] = append(db.pullTriggers[bin], trigger) 49 db.pullTriggersMu.Unlock() 50 51 // send signal for the initial iteration 52 trigger <- struct{}{} 53 54 stopChan := make(chan struct{}) 55 var stopChanOnce sync.Once 56 57 // used to provide information from the iterator to 58 // stop subscription when until chunk descriptor is reached 59 var errStopSubscription = errors.New("stop subscription") 60 61 go func() { 62 // close the returned ChunkDescriptor channel at the end to 63 // signal that the subscription is done 64 defer close(chunkDescriptors) 65 // sinceItem is the Item from which the next iteration 66 // should start. The first iteration starts from the first Item. 67 var sinceItem *shed.Item 68 if since != nil { 69 sinceItem = &shed.Item{ 70 Address: since.Address, 71 StoreTimestamp: since.StoreTimestamp, 72 } 73 } 74 for { 75 select { 76 case <-trigger: 77 // iterate until: 78 // - last index Item is reached 79 // - subscription stop is called 80 // - context is done 81 err := db.pullIndex.Iterate(func(item shed.Item) (stop bool, err error) { 82 select { 83 case chunkDescriptors <- ChunkDescriptor{ 84 Address: item.Address, 85 StoreTimestamp: item.StoreTimestamp, 86 }: 87 // until chunk descriptor is sent 88 // break the iteration 89 if until != nil && 90 (item.StoreTimestamp >= until.StoreTimestamp || 91 bytes.Equal(item.Address, until.Address)) { 92 return true, errStopSubscription 93 } 94 // set next iteration start item 95 // when its chunk is successfully sent to channel 96 sinceItem = &item 97 return false, nil 98 case <-stopChan: 99 // gracefully stop the iteration 100 // on stop 101 return true, nil 102 case <-db.close: 103 // gracefully stop the iteration 104 // on database close 105 return true, nil 106 case <-ctx.Done(): 107 return true, ctx.Err() 108 } 109 }, &shed.IterateOptions{ 110 StartFrom: sinceItem, 111 // sinceItem was sent as the last Address in the previous 112 // iterator call, skip it in this one 113 SkipStartFromItem: true, 114 Prefix: []byte{bin}, 115 }) 116 if err != nil { 117 if err == errStopSubscription { 118 // stop subscription without any errors 119 // if until is reached 120 return 121 } 122 log.Error("localstore pull subscription iteration", "bin", bin, "since", since, "until", until, "err", err) 123 return 124 } 125 case <-stopChan: 126 // terminate the subscription 127 // on stop 128 return 129 case <-db.close: 130 // terminate the subscription 131 // on database close 132 return 133 case <-ctx.Done(): 134 err := ctx.Err() 135 if err != nil { 136 log.Error("localstore pull subscription", "bin", bin, "since", since, "until", until, "err", err) 137 } 138 return 139 } 140 } 141 }() 142 143 stop = func() { 144 stopChanOnce.Do(func() { 145 close(stopChan) 146 }) 147 148 db.pullTriggersMu.Lock() 149 defer db.pullTriggersMu.Unlock() 150 151 for i, t := range db.pullTriggers[bin] { 152 if t == trigger { 153 db.pullTriggers[bin] = append(db.pullTriggers[bin][:i], db.pullTriggers[bin][i+1:]...) 154 break 155 } 156 } 157 } 158 159 return chunkDescriptors, stop 160 } 161 162 // LastPullSubscriptionChunk returns ChunkDescriptor of the latest Chunk 163 // in pull syncing index for a provided bin. If there are no chunks in 164 // that bin, chunk.ErrChunkNotFound is returned. 165 func (db *DB) LastPullSubscriptionChunk(bin uint8) (c *ChunkDescriptor, err error) { 166 item, err := db.pullIndex.Last([]byte{bin}) 167 if err != nil { 168 if err == leveldb.ErrNotFound { 169 return nil, chunk.ErrChunkNotFound 170 } 171 return nil, err 172 } 173 return &ChunkDescriptor{ 174 Address: item.Address, 175 StoreTimestamp: item.StoreTimestamp, 176 }, nil 177 } 178 179 // ChunkDescriptor holds information required for Pull syncing. This struct 180 // is provided by subscribing to pull index. 181 type ChunkDescriptor struct { 182 Address chunk.Address 183 StoreTimestamp int64 184 } 185 186 func (c *ChunkDescriptor) String() string { 187 if c == nil { 188 return "none" 189 } 190 return fmt.Sprintf("%s stored at %v", c.Address.Hex(), c.StoreTimestamp) 191 } 192 193 // triggerPullSubscriptions is used internally for starting iterations 194 // on Pull subscriptions for a particular bin. When new item with address 195 // that is in particular bin for DB's baseKey is added to pull index 196 // this function should be called. 197 func (db *DB) triggerPullSubscriptions(bin uint8) { 198 db.pullTriggersMu.RLock() 199 triggers, ok := db.pullTriggers[bin] 200 db.pullTriggersMu.RUnlock() 201 if !ok { 202 return 203 } 204 205 for _, t := range triggers { 206 select { 207 case t <- struct{}{}: 208 default: 209 } 210 } 211 }