github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/network/syncdb.go (about)

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