github.com/xxRanger/go-ethereum@v1.8.23/swarm/storage/localstore/subscription_push.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  	"context"
    21  	"sync"
    22  
    23  	"github.com/ethereum/go-ethereum/log"
    24  	"github.com/ethereum/go-ethereum/swarm/shed"
    25  	"github.com/ethereum/go-ethereum/swarm/storage"
    26  )
    27  
    28  // SubscribePush returns a channel that provides storage chunks with ordering from push syncing index.
    29  // Returned stop function will terminate current and further iterations, and also it will close
    30  // the returned channel without any errors. Make sure that you check the second returned parameter
    31  // from the channel to stop iteration when its value is false.
    32  func (db *DB) SubscribePush(ctx context.Context) (c <-chan storage.Chunk, stop func()) {
    33  	chunks := make(chan storage.Chunk)
    34  	trigger := make(chan struct{}, 1)
    35  
    36  	db.pushTriggersMu.Lock()
    37  	db.pushTriggers = append(db.pushTriggers, trigger)
    38  	db.pushTriggersMu.Unlock()
    39  
    40  	// send signal for the initial iteration
    41  	trigger <- struct{}{}
    42  
    43  	stopChan := make(chan struct{})
    44  	var stopChanOnce sync.Once
    45  
    46  	go func() {
    47  		// close the returned chunkInfo channel at the end to
    48  		// signal that the subscription is done
    49  		defer close(chunks)
    50  		// sinceItem is the Item from which the next iteration
    51  		// should start. The first iteration starts from the first Item.
    52  		var sinceItem *shed.Item
    53  		for {
    54  			select {
    55  			case <-trigger:
    56  				// iterate until:
    57  				// - last index Item is reached
    58  				// - subscription stop is called
    59  				// - context is done
    60  				err := db.pushIndex.Iterate(func(item shed.Item) (stop bool, err error) {
    61  					// get chunk data
    62  					dataItem, err := db.retrievalDataIndex.Get(item)
    63  					if err != nil {
    64  						return true, err
    65  					}
    66  
    67  					select {
    68  					case chunks <- storage.NewChunk(dataItem.Address, dataItem.Data):
    69  						// set next iteration start item
    70  						// when its chunk is successfully sent to channel
    71  						sinceItem = &item
    72  						return false, nil
    73  					case <-stopChan:
    74  						// gracefully stop the iteration
    75  						// on stop
    76  						return true, nil
    77  					case <-db.close:
    78  						// gracefully stop the iteration
    79  						// on database close
    80  						return true, nil
    81  					case <-ctx.Done():
    82  						return true, ctx.Err()
    83  					}
    84  				}, &shed.IterateOptions{
    85  					StartFrom: sinceItem,
    86  					// sinceItem was sent as the last Address in the previous
    87  					// iterator call, skip it in this one
    88  					SkipStartFromItem: true,
    89  				})
    90  				if err != nil {
    91  					log.Error("localstore push subscription iteration", "err", err)
    92  					return
    93  				}
    94  			case <-stopChan:
    95  				// terminate the subscription
    96  				// on stop
    97  				return
    98  			case <-db.close:
    99  				// terminate the subscription
   100  				// on database close
   101  				return
   102  			case <-ctx.Done():
   103  				err := ctx.Err()
   104  				if err != nil {
   105  					log.Error("localstore push subscription", "err", err)
   106  				}
   107  				return
   108  			}
   109  		}
   110  	}()
   111  
   112  	stop = func() {
   113  		stopChanOnce.Do(func() {
   114  			close(stopChan)
   115  		})
   116  
   117  		db.pushTriggersMu.Lock()
   118  		defer db.pushTriggersMu.Unlock()
   119  
   120  		for i, t := range db.pushTriggers {
   121  			if t == trigger {
   122  				db.pushTriggers = append(db.pushTriggers[:i], db.pushTriggers[i+1:]...)
   123  				break
   124  			}
   125  		}
   126  	}
   127  
   128  	return chunks, stop
   129  }
   130  
   131  // triggerPushSubscriptions is used internally for starting iterations
   132  // on Push subscriptions. Whenever new item is added to the push index,
   133  // this function should be called.
   134  func (db *DB) triggerPushSubscriptions() {
   135  	db.pushTriggersMu.RLock()
   136  	triggers := db.pushTriggers
   137  	db.pushTriggersMu.RUnlock()
   138  
   139  	for _, t := range triggers {
   140  		select {
   141  		case t <- struct{}{}:
   142  		default:
   143  		}
   144  	}
   145  }