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  }