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