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