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  }