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  }