github.com/codingfuture/orig-energi3@v0.8.4/swarm/storage/localstore/subscription_pull.go (about) 1 // Copyright 2019 The Energi Core Authors 2 // Copyright 2018 The go-ethereum Authors 3 // This file is part of the Energi Core library. 4 // 5 // The Energi Core library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The Energi Core library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>. 17 18 package localstore 19 20 import ( 21 "bytes" 22 "context" 23 "errors" 24 "fmt" 25 "sync" 26 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/swarm/shed" 29 "github.com/ethereum/go-ethereum/swarm/storage" 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 // ChunkDescriptor holds information required for Pull syncing. This struct 163 // is provided by subscribing to pull index. 164 type ChunkDescriptor struct { 165 Address storage.Address 166 StoreTimestamp int64 167 } 168 169 func (c *ChunkDescriptor) String() string { 170 if c == nil { 171 return "none" 172 } 173 return fmt.Sprintf("%s stored at %v", c.Address.Hex(), c.StoreTimestamp) 174 } 175 176 // triggerPullSubscriptions is used internally for starting iterations 177 // on Pull subscriptions for a particular bin. When new item with address 178 // that is in particular bin for DB's baseKey is added to pull index 179 // this function should be called. 180 func (db *DB) triggerPullSubscriptions(bin uint8) { 181 db.pullTriggersMu.RLock() 182 triggers, ok := db.pullTriggers[bin] 183 db.pullTriggersMu.RUnlock() 184 if !ok { 185 return 186 } 187 188 for _, t := range triggers { 189 select { 190 case t <- struct{}{}: 191 default: 192 } 193 } 194 }