github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/network/syncdb.go (about)

     1  // Copyright 2016 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package network
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  
    23  	"github.com/SmartMeshFoundation/Spectrum/log"
    24  	"github.com/SmartMeshFoundation/Spectrum/swarm/storage"
    25  	"github.com/syndtr/goleveldb/leveldb"
    26  	"github.com/syndtr/goleveldb/leveldb/iterator"
    27  )
    28  
    29  const counterKeyPrefix = 0x01
    30  
    31  /*
    32  syncDb is a queueing service for outgoing deliveries.
    33  One instance per priority queue for each peer
    34  
    35  a syncDb instance maintains an in-memory buffer (of capacity bufferSize)
    36  once its in-memory buffer is full it switches to persisting in db
    37  and dbRead iterator iterates through the items keeping their order
    38  once the db read catches up (there is no more items in the db) then
    39  it switches back to in-memory buffer.
    40  
    41  when syncdb is stopped all items in the buffer are saved to the db
    42  */
    43  type syncDb struct {
    44  	start          []byte               // this syncdb starting index in requestdb
    45  	key            storage.Key          // remote peers address key
    46  	counterKey     []byte               // db key to persist counter
    47  	priority       uint                 // priotity High|Medium|Low
    48  	buffer         chan interface{}     // incoming request channel
    49  	db             *storage.LDBDatabase // underlying db (TODO should be interface)
    50  	done           chan bool            // chan to signal goroutines finished quitting
    51  	quit           chan bool            // chan to signal quitting to goroutines
    52  	total, dbTotal int                  // counts for one session
    53  	batch          chan chan int        // channel for batch requests
    54  	dbBatchSize    uint                 // number of items before batch is saved
    55  }
    56  
    57  // constructor needs a shared request db (leveldb)
    58  // priority is used in the index key
    59  // uses a buffer and a leveldb for persistent storage
    60  // bufferSize, dbBatchSize are config parameters
    61  func newSyncDb(db *storage.LDBDatabase, key storage.Key, priority uint, bufferSize, dbBatchSize uint, deliver func(interface{}, chan bool) bool) *syncDb {
    62  	start := make([]byte, 42)
    63  	start[1] = byte(priorities - priority)
    64  	copy(start[2:34], key)
    65  
    66  	counterKey := make([]byte, 34)
    67  	counterKey[0] = counterKeyPrefix
    68  	copy(counterKey[1:], start[1:34])
    69  
    70  	syncdb := &syncDb{
    71  		start:       start,
    72  		key:         key,
    73  		counterKey:  counterKey,
    74  		priority:    priority,
    75  		buffer:      make(chan interface{}, bufferSize),
    76  		db:          db,
    77  		done:        make(chan bool),
    78  		quit:        make(chan bool),
    79  		batch:       make(chan chan int),
    80  		dbBatchSize: dbBatchSize,
    81  	}
    82  	log.Trace(fmt.Sprintf("syncDb[peer: %v, priority: %v] - initialised", key.Log(), priority))
    83  
    84  	// starts the main forever loop reading from buffer
    85  	go syncdb.bufferRead(deliver)
    86  	return syncdb
    87  }
    88  
    89  /*
    90  bufferRead is a forever iterator loop that takes care of delivering
    91  outgoing store requests reads from incoming buffer
    92  
    93  its argument is the deliver function taking the item as first argument
    94  and a quit channel as second.
    95  Closing of this channel is supposed to abort all waiting for delivery
    96  (typically network write)
    97  
    98  The iteration switches between 2 modes,
    99  * buffer mode reads the in-memory buffer and delivers the items directly
   100  * db mode reads from the buffer and writes to the db, parallelly another
   101  routine is started that reads from the db and delivers items
   102  
   103  If there is buffer contention in buffer mode (slow network, high upload volume)
   104  syncdb switches to db mode and starts dbRead
   105  Once db backlog is delivered, it reverts back to in-memory buffer
   106  
   107  It is automatically started when syncdb is initialised.
   108  
   109  It saves the buffer to db upon receiving quit signal. syncDb#stop()
   110  */
   111  func (self *syncDb) bufferRead(deliver func(interface{}, chan bool) bool) {
   112  	var buffer, db chan interface{} // channels representing the two read modes
   113  	var more bool
   114  	var req interface{}
   115  	var entry *syncDbEntry
   116  	var inBatch, inDb int
   117  	batch := new(leveldb.Batch)
   118  	var dbSize chan int
   119  	quit := self.quit
   120  	counterValue := make([]byte, 8)
   121  
   122  	// counter is used for keeping the items in order, persisted to db
   123  	// start counter where db was at, 0 if not found
   124  	data, err := self.db.Get(self.counterKey)
   125  	var counter uint64
   126  	if err == nil {
   127  		counter = binary.BigEndian.Uint64(data)
   128  		log.Trace(fmt.Sprintf("syncDb[%v/%v] - counter read from db at %v", self.key.Log(), self.priority, counter))
   129  	} else {
   130  		log.Trace(fmt.Sprintf("syncDb[%v/%v] - counter starts at %v", self.key.Log(), self.priority, counter))
   131  	}
   132  
   133  LOOP:
   134  	for {
   135  		// waiting for item next in the buffer, or quit signal or batch request
   136  		select {
   137  		// buffer only closes when writing to db
   138  		case req = <-buffer:
   139  			// deliver request : this is blocking on network write so
   140  			// it is passed the quit channel as argument, so that it returns
   141  			// if syncdb is stopped. In this case we need to save the item to the db
   142  			more = deliver(req, self.quit)
   143  			if !more {
   144  				log.Debug(fmt.Sprintf("syncDb[%v/%v] quit: switching to db. session tally (db/total): %v/%v", self.key.Log(), self.priority, self.dbTotal, self.total))
   145  				// received quit signal, save request currently waiting delivery
   146  				// by switching to db mode and closing the buffer
   147  				buffer = nil
   148  				db = self.buffer
   149  				close(db)
   150  				quit = nil // needs to block the quit case in select
   151  				break      // break from select, this item will be written to the db
   152  			}
   153  			self.total++
   154  			log.Trace(fmt.Sprintf("syncDb[%v/%v] deliver (db/total): %v/%v", self.key.Log(), self.priority, self.dbTotal, self.total))
   155  			// by the time deliver returns, there were new writes to the buffer
   156  			// if buffer contention is detected, switch to db mode which drains
   157  			// the buffer so no process will block on pushing store requests
   158  			if len(buffer) == cap(buffer) {
   159  				log.Debug(fmt.Sprintf("syncDb[%v/%v] buffer full %v: switching to db. session tally (db/total): %v/%v", self.key.Log(), self.priority, cap(buffer), self.dbTotal, self.total))
   160  				buffer = nil
   161  				db = self.buffer
   162  			}
   163  			continue LOOP
   164  
   165  			// incoming entry to put into db
   166  		case req, more = <-db:
   167  			if !more {
   168  				// only if quit is called, saved all the buffer
   169  				binary.BigEndian.PutUint64(counterValue, counter)
   170  				batch.Put(self.counterKey, counterValue) // persist counter in batch
   171  				self.writeSyncBatch(batch)               // save batch
   172  				log.Trace(fmt.Sprintf("syncDb[%v/%v] quitting: save current batch to db", self.key.Log(), self.priority))
   173  				break LOOP
   174  			}
   175  			self.dbTotal++
   176  			self.total++
   177  			// otherwise break after select
   178  		case dbSize = <-self.batch:
   179  			// explicit request for batch
   180  			if inBatch == 0 && quit != nil {
   181  				// there was no writes since the last batch so db depleted
   182  				// switch to buffer mode
   183  				log.Debug(fmt.Sprintf("syncDb[%v/%v] empty db: switching to buffer", self.key.Log(), self.priority))
   184  				db = nil
   185  				buffer = self.buffer
   186  				dbSize <- 0 // indicates to 'caller' that batch has been written
   187  				inDb = 0
   188  				continue LOOP
   189  			}
   190  			binary.BigEndian.PutUint64(counterValue, counter)
   191  			batch.Put(self.counterKey, counterValue)
   192  			log.Debug(fmt.Sprintf("syncDb[%v/%v] write batch %v/%v - %x - %x", self.key.Log(), self.priority, inBatch, counter, self.counterKey, counterValue))
   193  			batch = self.writeSyncBatch(batch)
   194  			dbSize <- inBatch // indicates to 'caller' that batch has been written
   195  			inBatch = 0
   196  			continue LOOP
   197  
   198  			// closing syncDb#quit channel is used to signal to all goroutines to quit
   199  		case <-quit:
   200  			// need to save backlog, so switch to db mode
   201  			db = self.buffer
   202  			buffer = nil
   203  			quit = nil
   204  			log.Trace(fmt.Sprintf("syncDb[%v/%v] quitting: save buffer to db", self.key.Log(), self.priority))
   205  			close(db)
   206  			continue LOOP
   207  		}
   208  
   209  		// only get here if we put req into db
   210  		entry, err = self.newSyncDbEntry(req, counter)
   211  		if err != nil {
   212  			log.Warn(fmt.Sprintf("syncDb[%v/%v] saving request %v (#%v/%v) failed: %v", self.key.Log(), self.priority, req, inBatch, inDb, err))
   213  			continue LOOP
   214  		}
   215  		batch.Put(entry.key, entry.val)
   216  		log.Trace(fmt.Sprintf("syncDb[%v/%v] to batch %v '%v' (#%v/%v/%v)", self.key.Log(), self.priority, req, entry, inBatch, inDb, counter))
   217  		// if just switched to db mode and not quitting, then launch dbRead
   218  		// in a parallel go routine to send deliveries from db
   219  		if inDb == 0 && quit != nil {
   220  			log.Trace(fmt.Sprintf("syncDb[%v/%v] start dbRead", self.key.Log(), self.priority))
   221  			go self.dbRead(true, counter, deliver)
   222  		}
   223  		inDb++
   224  		inBatch++
   225  		counter++
   226  		// need to save the batch if it gets too large (== dbBatchSize)
   227  		if inBatch%int(self.dbBatchSize) == 0 {
   228  			batch = self.writeSyncBatch(batch)
   229  		}
   230  	}
   231  	log.Info(fmt.Sprintf("syncDb[%v:%v]: saved %v keys (saved counter at %v)", self.key.Log(), self.priority, inBatch, counter))
   232  	close(self.done)
   233  }
   234  
   235  // writes the batch to the db and returns a new batch object
   236  func (self *syncDb) writeSyncBatch(batch *leveldb.Batch) *leveldb.Batch {
   237  	err := self.db.Write(batch)
   238  	if err != nil {
   239  		log.Warn(fmt.Sprintf("syncDb[%v/%v] saving batch to db failed: %v", self.key.Log(), self.priority, err))
   240  		return batch
   241  	}
   242  	return new(leveldb.Batch)
   243  }
   244  
   245  // abstract type for db entries (TODO could be a feature of Receipts)
   246  type syncDbEntry struct {
   247  	key, val []byte
   248  }
   249  
   250  func (self syncDbEntry) String() string {
   251  	return fmt.Sprintf("key: %x, value: %x", self.key, self.val)
   252  }
   253  
   254  /*
   255  	dbRead is iterating over store requests to be sent over to the peer
   256  	this is mainly to prevent crashes due to network output buffer contention (???)
   257  	as well as to make syncronisation resilient to disconnects
   258  	the messages are supposed to be sent in the p2p priority queue.
   259  
   260  	the request DB is shared between peers, but domains for each syncdb
   261  	are disjoint. dbkeys (42 bytes) are structured:
   262  	* 0: 0x00 (0x01 reserved for counter key)
   263  	* 1: priorities - priority (so that high priority can be replayed first)
   264  	* 2-33: peers address
   265  	* 34-41: syncdb counter to preserve order (this field is missing for the counter key)
   266  
   267  	values (40 bytes) are:
   268  	* 0-31: key
   269  	* 32-39: request id
   270  
   271  dbRead needs a boolean to indicate if on first round all the historical
   272  record is synced. Second argument to indicate current db counter
   273  The third is the function to apply
   274  */
   275  func (self *syncDb) dbRead(useBatches bool, counter uint64, fun func(interface{}, chan bool) bool) {
   276  	key := make([]byte, 42)
   277  	copy(key, self.start)
   278  	binary.BigEndian.PutUint64(key[34:], counter)
   279  	var batches, n, cnt, total int
   280  	var more bool
   281  	var entry *syncDbEntry
   282  	var it iterator.Iterator
   283  	var del *leveldb.Batch
   284  	batchSizes := make(chan int)
   285  
   286  	for {
   287  		// if useBatches is false, cnt is not set
   288  		if useBatches {
   289  			// this could be called before all cnt items sent out
   290  			// so that loop is not blocking while delivering
   291  			// only relevant if cnt is large
   292  			select {
   293  			case self.batch <- batchSizes:
   294  			case <-self.quit:
   295  				return
   296  			}
   297  			// wait for the write to finish and get the item count in the next batch
   298  			cnt = <-batchSizes
   299  			batches++
   300  			if cnt == 0 {
   301  				// empty
   302  				return
   303  			}
   304  		}
   305  		it = self.db.NewIterator()
   306  		it.Seek(key)
   307  		if !it.Valid() {
   308  			copy(key, self.start)
   309  			useBatches = true
   310  			continue
   311  		}
   312  		del = new(leveldb.Batch)
   313  		log.Trace(fmt.Sprintf("syncDb[%v/%v]: new iterator: %x (batch %v, count %v)", self.key.Log(), self.priority, key, batches, cnt))
   314  
   315  		for n = 0; !useBatches || n < cnt; it.Next() {
   316  			copy(key, it.Key())
   317  			if len(key) == 0 || key[0] != 0 {
   318  				copy(key, self.start)
   319  				useBatches = true
   320  				break
   321  			}
   322  			val := make([]byte, 40)
   323  			copy(val, it.Value())
   324  			entry = &syncDbEntry{key, val}
   325  			// log.Trace(fmt.Sprintf("syncDb[%v/%v] - %v, batches: %v, total: %v, session total from db: %v/%v", self.key.Log(), self.priority, self.key.Log(), batches, total, self.dbTotal, self.total))
   326  			more = fun(entry, self.quit)
   327  			if !more {
   328  				// quit received when waiting to deliver entry, the entry will not be deleted
   329  				log.Trace(fmt.Sprintf("syncDb[%v/%v] batch %v quit after %v/%v items", self.key.Log(), self.priority, batches, n, cnt))
   330  				break
   331  			}
   332  			// since subsequent batches of the same db session are indexed incrementally
   333  			// deleting earlier batches can be delayed and parallelised
   334  			// this could be batch delete when db is idle (but added complexity esp when quitting)
   335  			del.Delete(key)
   336  			n++
   337  			total++
   338  		}
   339  		log.Debug(fmt.Sprintf("syncDb[%v/%v] - db session closed, batches: %v, total: %v, session total from db: %v/%v", self.key.Log(), self.priority, batches, total, self.dbTotal, self.total))
   340  		self.db.Write(del) // this could be async called only when db is idle
   341  		it.Release()
   342  	}
   343  }
   344  
   345  //
   346  func (self *syncDb) stop() {
   347  	close(self.quit)
   348  	<-self.done
   349  }
   350  
   351  // calculate a dbkey for the request, for the db to work
   352  // see syncdb for db key structure
   353  // polimorphic: accepted types, see syncer#addRequest
   354  func (self *syncDb) newSyncDbEntry(req interface{}, counter uint64) (entry *syncDbEntry, err error) {
   355  	var key storage.Key
   356  	var chunk *storage.Chunk
   357  	var id uint64
   358  	var ok bool
   359  	var sreq *storeRequestMsgData
   360  
   361  	if key, ok = req.(storage.Key); ok {
   362  		id = generateId()
   363  	} else if chunk, ok = req.(*storage.Chunk); ok {
   364  		key = chunk.Key
   365  		id = generateId()
   366  	} else if sreq, ok = req.(*storeRequestMsgData); ok {
   367  		key = sreq.Key
   368  		id = sreq.Id
   369  	} else if entry, ok = req.(*syncDbEntry); !ok {
   370  		return nil, fmt.Errorf("type not allowed: %v (%T)", req, req)
   371  	}
   372  
   373  	// order by peer > priority > seqid
   374  	// value is request id if exists
   375  	if entry == nil {
   376  		dbkey := make([]byte, 42)
   377  		dbval := make([]byte, 40)
   378  
   379  		// encode key
   380  		copy(dbkey[:], self.start[:34]) // db  peer
   381  		binary.BigEndian.PutUint64(dbkey[34:], counter)
   382  		// encode value
   383  		copy(dbval, key[:])
   384  		binary.BigEndian.PutUint64(dbval[32:], id)
   385  
   386  		entry = &syncDbEntry{dbkey, dbval}
   387  	}
   388  	return
   389  }