github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/swarm/storage/pyramid.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 12:09:50</date>
    10  //</624342684378599424>
    11  
    12  //
    13  //
    14  //
    15  //
    16  //
    17  //
    18  //
    19  //
    20  //
    21  //
    22  //
    23  //
    24  //
    25  //
    26  //
    27  
    28  package storage
    29  
    30  import (
    31  	"context"
    32  	"encoding/binary"
    33  	"errors"
    34  	"io"
    35  	"io/ioutil"
    36  	"sync"
    37  	"time"
    38  
    39  	"github.com/ethereum/go-ethereum/swarm/chunk"
    40  	"github.com/ethereum/go-ethereum/swarm/log"
    41  )
    42  
    43  /*
    44     
    45     
    46     
    47     
    48  
    49  
    50     
    51     
    52     
    53  
    54  
    55  
    56                                              
    57                                              
    58                    
    59                   
    60                  
    61              
    62             
    63            
    64           
    65  
    66  
    67      
    68      
    69      
    70      
    71      
    72  
    73  */
    74  
    75  
    76  var (
    77  	errLoadingTreeRootChunk = errors.New("LoadTree Error: Could not load root chunk")
    78  	errLoadingTreeChunk     = errors.New("LoadTree Error: Could not load chunk")
    79  )
    80  
    81  const (
    82  	ChunkProcessors = 8
    83  	splitTimeout    = time.Minute * 5
    84  )
    85  
    86  const (
    87  	DataChunk = 0
    88  	TreeChunk = 1
    89  )
    90  
    91  type PyramidSplitterParams struct {
    92  	SplitterParams
    93  	getter Getter
    94  }
    95  
    96  func NewPyramidSplitterParams(addr Address, reader io.Reader, putter Putter, getter Getter, chunkSize int64) *PyramidSplitterParams {
    97  	hashSize := putter.RefSize()
    98  	return &PyramidSplitterParams{
    99  		SplitterParams: SplitterParams{
   100  			ChunkerParams: ChunkerParams{
   101  				chunkSize: chunkSize,
   102  				hashSize:  hashSize,
   103  			},
   104  			reader: reader,
   105  			putter: putter,
   106  			addr:   addr,
   107  		},
   108  		getter: getter,
   109  	}
   110  }
   111  
   112  /*
   113   
   114   
   115  */
   116  
   117  func PyramidSplit(ctx context.Context, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) {
   118  	return NewPyramidSplitter(NewPyramidSplitterParams(nil, reader, putter, getter, chunk.DefaultSize)).Split(ctx)
   119  }
   120  
   121  func PyramidAppend(ctx context.Context, addr Address, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) {
   122  	return NewPyramidSplitter(NewPyramidSplitterParams(addr, reader, putter, getter, chunk.DefaultSize)).Append(ctx)
   123  }
   124  
   125  //
   126  type TreeEntry struct {
   127  	level         int
   128  	branchCount   int64
   129  	subtreeSize   uint64
   130  	chunk         []byte
   131  	key           []byte
   132  index         int  //
   133  updatePending bool //
   134  }
   135  
   136  func NewTreeEntry(pyramid *PyramidChunker) *TreeEntry {
   137  	return &TreeEntry{
   138  		level:         0,
   139  		branchCount:   0,
   140  		subtreeSize:   0,
   141  		chunk:         make([]byte, pyramid.chunkSize+8),
   142  		key:           make([]byte, pyramid.hashSize),
   143  		index:         0,
   144  		updatePending: false,
   145  	}
   146  }
   147  
   148  //
   149  type chunkJob struct {
   150  	key      Address
   151  	chunk    []byte
   152  	parentWg *sync.WaitGroup
   153  }
   154  
   155  type PyramidChunker struct {
   156  	chunkSize   int64
   157  	hashSize    int64
   158  	branches    int64
   159  	reader      io.Reader
   160  	putter      Putter
   161  	getter      Getter
   162  	key         Address
   163  	workerCount int64
   164  	workerLock  sync.RWMutex
   165  	jobC        chan *chunkJob
   166  	wg          *sync.WaitGroup
   167  	errC        chan error
   168  	quitC       chan bool
   169  	rootKey     []byte
   170  	chunkLevel  [][]*TreeEntry
   171  }
   172  
   173  func NewPyramidSplitter(params *PyramidSplitterParams) (pc *PyramidChunker) {
   174  	pc = &PyramidChunker{}
   175  	pc.reader = params.reader
   176  	pc.hashSize = params.hashSize
   177  	pc.branches = params.chunkSize / pc.hashSize
   178  	pc.chunkSize = pc.hashSize * pc.branches
   179  	pc.putter = params.putter
   180  	pc.getter = params.getter
   181  	pc.key = params.addr
   182  	pc.workerCount = 0
   183  	pc.jobC = make(chan *chunkJob, 2*ChunkProcessors)
   184  	pc.wg = &sync.WaitGroup{}
   185  	pc.errC = make(chan error)
   186  	pc.quitC = make(chan bool)
   187  	pc.rootKey = make([]byte, pc.hashSize)
   188  	pc.chunkLevel = make([][]*TreeEntry, pc.branches)
   189  	return
   190  }
   191  
   192  func (pc *PyramidChunker) Join(addr Address, getter Getter, depth int) LazySectionReader {
   193  	return &LazyChunkReader{
   194  		key:       addr,
   195  		depth:     depth,
   196  		chunkSize: pc.chunkSize,
   197  		branches:  pc.branches,
   198  		hashSize:  pc.hashSize,
   199  		getter:    getter,
   200  	}
   201  }
   202  
   203  func (pc *PyramidChunker) incrementWorkerCount() {
   204  	pc.workerLock.Lock()
   205  	defer pc.workerLock.Unlock()
   206  	pc.workerCount += 1
   207  }
   208  
   209  func (pc *PyramidChunker) getWorkerCount() int64 {
   210  	pc.workerLock.Lock()
   211  	defer pc.workerLock.Unlock()
   212  	return pc.workerCount
   213  }
   214  
   215  func (pc *PyramidChunker) decrementWorkerCount() {
   216  	pc.workerLock.Lock()
   217  	defer pc.workerLock.Unlock()
   218  	pc.workerCount -= 1
   219  }
   220  
   221  func (pc *PyramidChunker) Split(ctx context.Context) (k Address, wait func(context.Context) error, err error) {
   222  	log.Debug("pyramid.chunker: Split()")
   223  
   224  	pc.wg.Add(1)
   225  	pc.prepareChunks(false)
   226  
   227  //
   228  	go func() {
   229  
   230  //
   231  		pc.wg.Wait()
   232  
   233  //
   234  //
   235  //
   236  		close(pc.errC)
   237  	}()
   238  
   239  	defer close(pc.quitC)
   240  	defer pc.putter.Close()
   241  
   242  	select {
   243  	case err := <-pc.errC:
   244  		if err != nil {
   245  			return nil, nil, err
   246  		}
   247  	case <-time.NewTimer(splitTimeout).C:
   248  	}
   249  	return pc.rootKey, pc.putter.Wait, nil
   250  
   251  }
   252  
   253  func (pc *PyramidChunker) Append(ctx context.Context) (k Address, wait func(context.Context) error, err error) {
   254  	log.Debug("pyramid.chunker: Append()")
   255  //
   256  	pc.loadTree()
   257  
   258  	pc.wg.Add(1)
   259  	pc.prepareChunks(true)
   260  
   261  //
   262  	go func() {
   263  
   264  //
   265  		pc.wg.Wait()
   266  
   267  		close(pc.errC)
   268  	}()
   269  
   270  	defer close(pc.quitC)
   271  	defer pc.putter.Close()
   272  
   273  	select {
   274  	case err := <-pc.errC:
   275  		if err != nil {
   276  			return nil, nil, err
   277  		}
   278  	case <-time.NewTimer(splitTimeout).C:
   279  	}
   280  
   281  	return pc.rootKey, pc.putter.Wait, nil
   282  
   283  }
   284  
   285  func (pc *PyramidChunker) processor(id int64) {
   286  	defer pc.decrementWorkerCount()
   287  	for {
   288  		select {
   289  
   290  		case job, ok := <-pc.jobC:
   291  			if !ok {
   292  				return
   293  			}
   294  			pc.processChunk(id, job)
   295  		case <-pc.quitC:
   296  			return
   297  		}
   298  	}
   299  }
   300  
   301  func (pc *PyramidChunker) processChunk(id int64, job *chunkJob) {
   302  	log.Debug("pyramid.chunker: processChunk()", "id", id)
   303  
   304  	ref, err := pc.putter.Put(context.TODO(), job.chunk)
   305  	if err != nil {
   306  		pc.errC <- err
   307  	}
   308  
   309  //
   310  	copy(job.key, ref)
   311  
   312  //
   313  	job.parentWg.Done()
   314  }
   315  
   316  func (pc *PyramidChunker) loadTree() error {
   317  	log.Debug("pyramid.chunker: loadTree()")
   318  //
   319  	chunkData, err := pc.getter.Get(context.TODO(), Reference(pc.key))
   320  	if err != nil {
   321  		return errLoadingTreeRootChunk
   322  	}
   323  	chunkSize := chunkData.Size()
   324  	log.Trace("pyramid.chunker: root chunk", "chunk.Size", chunkSize, "pc.chunkSize", pc.chunkSize)
   325  
   326  //
   327  	if chunkSize <= pc.chunkSize {
   328  		newEntry := &TreeEntry{
   329  			level:         0,
   330  			branchCount:   1,
   331  			subtreeSize:   uint64(chunkSize),
   332  			chunk:         make([]byte, pc.chunkSize+8),
   333  			key:           make([]byte, pc.hashSize),
   334  			index:         0,
   335  			updatePending: true,
   336  		}
   337  		copy(newEntry.chunk[8:], pc.key)
   338  		pc.chunkLevel[0] = append(pc.chunkLevel[0], newEntry)
   339  		return nil
   340  	}
   341  
   342  	var treeSize int64
   343  	var depth int
   344  	treeSize = pc.chunkSize
   345  	for ; treeSize < chunkSize; treeSize *= pc.branches {
   346  		depth++
   347  	}
   348  	log.Trace("pyramid.chunker", "depth", depth)
   349  
   350  //
   351  	branchCount := int64(len(chunkData)-8) / pc.hashSize
   352  	newEntry := &TreeEntry{
   353  		level:         depth - 1,
   354  		branchCount:   branchCount,
   355  		subtreeSize:   uint64(chunkSize),
   356  		chunk:         chunkData,
   357  		key:           pc.key,
   358  		index:         0,
   359  		updatePending: true,
   360  	}
   361  	pc.chunkLevel[depth-1] = append(pc.chunkLevel[depth-1], newEntry)
   362  
   363  //
   364  	for lvl := depth - 1; lvl >= 1; lvl-- {
   365  
   366  //
   367  //
   368  		for _, ent := range pc.chunkLevel[lvl] {
   369  			branchCount = int64(len(ent.chunk)-8) / pc.hashSize
   370  			for i := int64(0); i < branchCount; i++ {
   371  				key := ent.chunk[8+(i*pc.hashSize) : 8+((i+1)*pc.hashSize)]
   372  				newChunkData, err := pc.getter.Get(context.TODO(), Reference(key))
   373  				if err != nil {
   374  					return errLoadingTreeChunk
   375  				}
   376  				newChunkSize := newChunkData.Size()
   377  				bewBranchCount := int64(len(newChunkData)-8) / pc.hashSize
   378  				newEntry := &TreeEntry{
   379  					level:         lvl - 1,
   380  					branchCount:   bewBranchCount,
   381  					subtreeSize:   uint64(newChunkSize),
   382  					chunk:         newChunkData,
   383  					key:           key,
   384  					index:         0,
   385  					updatePending: true,
   386  				}
   387  				pc.chunkLevel[lvl-1] = append(pc.chunkLevel[lvl-1], newEntry)
   388  
   389  			}
   390  
   391  //
   392  			if int64(len(pc.chunkLevel[lvl-1])) >= pc.branches {
   393  				pc.chunkLevel[lvl-1] = nil
   394  			}
   395  		}
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  func (pc *PyramidChunker) prepareChunks(isAppend bool) {
   402  	log.Debug("pyramid.chunker: prepareChunks", "isAppend", isAppend)
   403  	defer pc.wg.Done()
   404  
   405  	chunkWG := &sync.WaitGroup{}
   406  
   407  	pc.incrementWorkerCount()
   408  
   409  	go pc.processor(pc.workerCount)
   410  
   411  	parent := NewTreeEntry(pc)
   412  	var unfinishedChunkData ChunkData
   413  	var unfinishedChunkSize int64
   414  
   415  	if isAppend && len(pc.chunkLevel[0]) != 0 {
   416  		lastIndex := len(pc.chunkLevel[0]) - 1
   417  		ent := pc.chunkLevel[0][lastIndex]
   418  
   419  		if ent.branchCount < pc.branches {
   420  			parent = &TreeEntry{
   421  				level:         0,
   422  				branchCount:   ent.branchCount,
   423  				subtreeSize:   ent.subtreeSize,
   424  				chunk:         ent.chunk,
   425  				key:           ent.key,
   426  				index:         lastIndex,
   427  				updatePending: true,
   428  			}
   429  
   430  			lastBranch := parent.branchCount - 1
   431  			lastKey := parent.chunk[8+lastBranch*pc.hashSize : 8+(lastBranch+1)*pc.hashSize]
   432  
   433  			var err error
   434  			unfinishedChunkData, err = pc.getter.Get(context.TODO(), lastKey)
   435  			if err != nil {
   436  				pc.errC <- err
   437  			}
   438  			unfinishedChunkSize = unfinishedChunkData.Size()
   439  			if unfinishedChunkSize < pc.chunkSize {
   440  				parent.subtreeSize = parent.subtreeSize - uint64(unfinishedChunkSize)
   441  				parent.branchCount = parent.branchCount - 1
   442  			} else {
   443  				unfinishedChunkData = nil
   444  			}
   445  		}
   446  	}
   447  
   448  	for index := 0; ; index++ {
   449  		var err error
   450  		chunkData := make([]byte, pc.chunkSize+8)
   451  
   452  		var readBytes int
   453  
   454  		if unfinishedChunkData != nil {
   455  			copy(chunkData, unfinishedChunkData)
   456  			readBytes += int(unfinishedChunkSize)
   457  			unfinishedChunkData = nil
   458  			log.Trace("pyramid.chunker: found unfinished chunk", "readBytes", readBytes)
   459  		}
   460  
   461  		var res []byte
   462  		res, err = ioutil.ReadAll(io.LimitReader(pc.reader, int64(len(chunkData)-(8+readBytes))))
   463  
   464  //
   465  //
   466  //
   467  		if len(res) == 0 && err == nil {
   468  			err = io.EOF
   469  		}
   470  		copy(chunkData[8+readBytes:], res)
   471  
   472  		readBytes += len(res)
   473  		log.Trace("pyramid.chunker: copied all data", "readBytes", readBytes)
   474  
   475  		if err != nil {
   476  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   477  
   478  				pc.cleanChunkLevels()
   479  
   480  //
   481  				if parent.branchCount == 1 && (pc.depth() == 0 || isAppend) {
   482  //
   483  					chunkWG.Wait()
   484  					lastChunksKey := parent.chunk[8 : 8+pc.hashSize]
   485  					copy(pc.rootKey, lastChunksKey)
   486  					break
   487  				}
   488  			} else {
   489  				close(pc.quitC)
   490  				break
   491  			}
   492  		}
   493  
   494  //
   495  		if readBytes == 0 {
   496  			pc.buildTree(isAppend, parent, chunkWG, true, nil)
   497  			break
   498  		} else {
   499  			pkey := pc.enqueueDataChunk(chunkData, uint64(readBytes), parent, chunkWG)
   500  
   501  //
   502  			parent.subtreeSize += uint64(readBytes)
   503  			parent.branchCount++
   504  
   505  //
   506  			if int64(readBytes) < pc.chunkSize {
   507  
   508  				pc.cleanChunkLevels()
   509  
   510  //
   511  				if parent.branchCount <= 1 {
   512  					chunkWG.Wait()
   513  
   514  					if isAppend || pc.depth() == 0 {
   515  //
   516  //
   517  //
   518  						copy(pc.rootKey, pkey)
   519  					} else {
   520  //
   521  //
   522  						pc.buildTree(isAppend, parent, chunkWG, true, pkey)
   523  					}
   524  					break
   525  				}
   526  
   527  				pc.buildTree(isAppend, parent, chunkWG, true, nil)
   528  				break
   529  			}
   530  
   531  			if parent.branchCount == pc.branches {
   532  				pc.buildTree(isAppend, parent, chunkWG, false, nil)
   533  				parent = NewTreeEntry(pc)
   534  			}
   535  
   536  		}
   537  
   538  		workers := pc.getWorkerCount()
   539  		if int64(len(pc.jobC)) > workers && workers < ChunkProcessors {
   540  			pc.incrementWorkerCount()
   541  			go pc.processor(pc.workerCount)
   542  		}
   543  
   544  	}
   545  
   546  }
   547  
   548  func (pc *PyramidChunker) buildTree(isAppend bool, ent *TreeEntry, chunkWG *sync.WaitGroup, last bool, lonelyChunkKey []byte) {
   549  	chunkWG.Wait()
   550  	pc.enqueueTreeChunk(ent, chunkWG, last)
   551  
   552  	compress := false
   553  	endLvl := pc.branches
   554  	for lvl := int64(0); lvl < pc.branches; lvl++ {
   555  		lvlCount := int64(len(pc.chunkLevel[lvl]))
   556  		if lvlCount >= pc.branches {
   557  			endLvl = lvl + 1
   558  			compress = true
   559  			break
   560  		}
   561  	}
   562  
   563  	if !compress && !last {
   564  		return
   565  	}
   566  
   567  //
   568  	chunkWG.Wait()
   569  
   570  	for lvl := int64(ent.level); lvl < endLvl; lvl++ {
   571  
   572  		lvlCount := int64(len(pc.chunkLevel[lvl]))
   573  		if lvlCount == 1 && last {
   574  			copy(pc.rootKey, pc.chunkLevel[lvl][0].key)
   575  			return
   576  		}
   577  
   578  		for startCount := int64(0); startCount < lvlCount; startCount += pc.branches {
   579  
   580  			endCount := startCount + pc.branches
   581  			if endCount > lvlCount {
   582  				endCount = lvlCount
   583  			}
   584  
   585  			var nextLvlCount int64
   586  			var tempEntry *TreeEntry
   587  			if len(pc.chunkLevel[lvl+1]) > 0 {
   588  				nextLvlCount = int64(len(pc.chunkLevel[lvl+1]) - 1)
   589  				tempEntry = pc.chunkLevel[lvl+1][nextLvlCount]
   590  			}
   591  			if isAppend && tempEntry != nil && tempEntry.updatePending {
   592  				updateEntry := &TreeEntry{
   593  					level:         int(lvl + 1),
   594  					branchCount:   0,
   595  					subtreeSize:   0,
   596  					chunk:         make([]byte, pc.chunkSize+8),
   597  					key:           make([]byte, pc.hashSize),
   598  					index:         int(nextLvlCount),
   599  					updatePending: true,
   600  				}
   601  				for index := int64(0); index < lvlCount; index++ {
   602  					updateEntry.branchCount++
   603  					updateEntry.subtreeSize += pc.chunkLevel[lvl][index].subtreeSize
   604  					copy(updateEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], pc.chunkLevel[lvl][index].key[:pc.hashSize])
   605  				}
   606  
   607  				pc.enqueueTreeChunk(updateEntry, chunkWG, last)
   608  
   609  			} else {
   610  
   611  				noOfBranches := endCount - startCount
   612  				newEntry := &TreeEntry{
   613  					level:         int(lvl + 1),
   614  					branchCount:   noOfBranches,
   615  					subtreeSize:   0,
   616  					chunk:         make([]byte, (noOfBranches*pc.hashSize)+8),
   617  					key:           make([]byte, pc.hashSize),
   618  					index:         int(nextLvlCount),
   619  					updatePending: false,
   620  				}
   621  
   622  				index := int64(0)
   623  				for i := startCount; i < endCount; i++ {
   624  					entry := pc.chunkLevel[lvl][i]
   625  					newEntry.subtreeSize += entry.subtreeSize
   626  					copy(newEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], entry.key[:pc.hashSize])
   627  					index++
   628  				}
   629  //
   630  //
   631  				if lonelyChunkKey != nil {
   632  //
   633  					copy(newEntry.chunk[int64(len(newEntry.chunk))-pc.hashSize:], lonelyChunkKey[:pc.hashSize])
   634  				}
   635  
   636  				pc.enqueueTreeChunk(newEntry, chunkWG, last)
   637  
   638  			}
   639  
   640  		}
   641  
   642  		if !isAppend {
   643  			chunkWG.Wait()
   644  			if compress {
   645  				pc.chunkLevel[lvl] = nil
   646  			}
   647  		}
   648  	}
   649  
   650  }
   651  
   652  func (pc *PyramidChunker) enqueueTreeChunk(ent *TreeEntry, chunkWG *sync.WaitGroup, last bool) {
   653  	if ent != nil && ent.branchCount > 0 {
   654  
   655  //
   656  		if last {
   657  			chunkWG.Wait()
   658  		}
   659  
   660  		binary.LittleEndian.PutUint64(ent.chunk[:8], ent.subtreeSize)
   661  		ent.key = make([]byte, pc.hashSize)
   662  		chunkWG.Add(1)
   663  		select {
   664  		case pc.jobC <- &chunkJob{ent.key, ent.chunk[:ent.branchCount*pc.hashSize+8], chunkWG}:
   665  		case <-pc.quitC:
   666  		}
   667  
   668  //
   669  		if ent.updatePending {
   670  			chunkWG.Wait()
   671  			pc.chunkLevel[ent.level][ent.index] = ent
   672  		} else {
   673  			pc.chunkLevel[ent.level] = append(pc.chunkLevel[ent.level], ent)
   674  		}
   675  
   676  	}
   677  }
   678  
   679  func (pc *PyramidChunker) enqueueDataChunk(chunkData []byte, size uint64, parent *TreeEntry, chunkWG *sync.WaitGroup) Address {
   680  	binary.LittleEndian.PutUint64(chunkData[:8], size)
   681  	pkey := parent.chunk[8+parent.branchCount*pc.hashSize : 8+(parent.branchCount+1)*pc.hashSize]
   682  
   683  	chunkWG.Add(1)
   684  	select {
   685  	case pc.jobC <- &chunkJob{pkey, chunkData[:size+8], chunkWG}:
   686  	case <-pc.quitC:
   687  	}
   688  
   689  	return pkey
   690  
   691  }
   692  
   693  //
   694  //
   695  //
   696  func (pc *PyramidChunker) depth() (d int) {
   697  	for _, l := range pc.chunkLevel {
   698  		if l == nil {
   699  			return
   700  		}
   701  		d++
   702  	}
   703  	return
   704  }
   705  
   706  //
   707  //
   708  func (pc *PyramidChunker) cleanChunkLevels() {
   709  	for i, l := range pc.chunkLevel {
   710  		if l == nil {
   711  			pc.chunkLevel = append(pc.chunkLevel[:i], append(pc.chunkLevel[i+1:], nil)...)
   712  		}
   713  	}
   714  }
   715