github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/queue/priority_queue.go (about)

     1  package queue
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"github.com/angenalZZZ/gofunc/f"
     7  	"os"
     8  	"sync"
     9  
    10  	"github.com/syndtr/goleveldb/leveldb"
    11  	"github.com/syndtr/goleveldb/leveldb/util"
    12  )
    13  
    14  // prefixSep is the prefix separator for each item key.
    15  var prefixSep []byte = []byte(":")
    16  
    17  // order defines the priority ordering of the queue.
    18  type order int
    19  
    20  // Defines which priority order to dequeue in.
    21  const (
    22  	ASC  order = iota // SetHeader priority level 0 as most important.
    23  	DESC              // SetHeader priority level 255 as most important.
    24  )
    25  
    26  // priorityLevel holds the head and tail position of a priority
    27  // level within the queue.
    28  type priorityLevel struct {
    29  	head uint64
    30  	tail uint64
    31  }
    32  
    33  // length returns the total number of items in this priority level.
    34  func (pl *priorityLevel) length() uint64 {
    35  	return pl.tail - pl.head
    36  }
    37  
    38  // PriorityQueue is a standard FIFO (first in, first out) queue with
    39  // priority levels.
    40  type PriorityQueue struct {
    41  	sync.RWMutex
    42  	DataDir  string
    43  	db       *leveldb.DB
    44  	order    order
    45  	levels   [256]*priorityLevel
    46  	curLevel uint8
    47  	isOpen   bool
    48  }
    49  
    50  // OpenPriorityQueue opens a priority queue if one exists at the given
    51  // directory. If one does not already exist, a new priority queue is
    52  // created.
    53  func OpenPriorityQueue(dataDir string, order order) (*PriorityQueue, error) {
    54  	var err error
    55  
    56  	// Create a new PriorityQueue.
    57  	pq := &PriorityQueue{
    58  		DataDir: dataDir,
    59  		db:      &leveldb.DB{},
    60  		order:   order,
    61  		isOpen:  false,
    62  	}
    63  
    64  	// Open database for the priority queue.
    65  	pq.db, err = leveldb.OpenFile(dataDir, nil)
    66  	if err != nil {
    67  		return pq, err
    68  	}
    69  
    70  	// Check if this queue type can open the requested data directory.
    71  	ok, err := checkQueueType(dataDir, queuePriorityQueue)
    72  	if err != nil {
    73  		return pq, err
    74  	}
    75  	if !ok {
    76  		return pq, ErrIncompatibleType
    77  	}
    78  
    79  	// SetHeader isOpen and return.
    80  	pq.isOpen = true
    81  	return pq, pq.init()
    82  }
    83  
    84  // Enqueue adds an item to the priority queue.
    85  func (pq *PriorityQueue) Enqueue(priority uint8, value []byte) (*PriorityItem, error) {
    86  	pq.Lock()
    87  	defer pq.Unlock()
    88  
    89  	// Check if queue is closed.
    90  	if !pq.isOpen {
    91  		return nil, ErrDBClosed
    92  	}
    93  
    94  	// GetHeader the priorityLevel.
    95  	level := pq.levels[priority]
    96  
    97  	// Create new PriorityItem.
    98  	item := &PriorityItem{
    99  		ID:       level.tail + 1,
   100  		Priority: priority,
   101  		Key:      pq.generateKey(priority, level.tail+1),
   102  		Value:    value,
   103  	}
   104  
   105  	// Add it to the priority queue.
   106  	if err := pq.db.Put(item.Key, item.Value, nil); err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	// Increment tail position.
   111  	level.tail++
   112  
   113  	// If this priority level is more important than the curLevel.
   114  	if pq.cmpAsc(priority) || pq.cmpDesc(priority) {
   115  		pq.curLevel = priority
   116  	}
   117  
   118  	return item, nil
   119  }
   120  
   121  // EnqueueString is a helper function for Enqueue that accepts a
   122  // value as a string rather than a byte slice.
   123  func (pq *PriorityQueue) EnqueueString(priority uint8, value string) (*PriorityItem, error) {
   124  	return pq.Enqueue(priority, []byte(value))
   125  }
   126  
   127  // EnqueueObject is a helper function for Enqueue that accepts any
   128  // value type, which is then encoded into a byte slice using
   129  // encoding/gob.
   130  //
   131  // Objects containing pointers with zero values will decode to nil
   132  // when using this function. This is due to how the encoding/gob
   133  // package works. Because of this, you should only use this function
   134  // to encode simple types.
   135  func (pq *PriorityQueue) EnqueueObject(priority uint8, value interface{}) (*PriorityItem, error) {
   136  	var buffer bytes.Buffer
   137  	enc := gob.NewEncoder(&buffer)
   138  	if err := enc.Encode(value); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return pq.Enqueue(priority, buffer.Bytes())
   143  }
   144  
   145  // EnqueueObjectAsJSON is a helper function for Enqueue that accepts
   146  // any value type, which is then encoded into a JSON byte slice using
   147  // encoding/json.
   148  //
   149  // Use this function to handle encoding of complex types.
   150  func (pq *PriorityQueue) EnqueueObjectAsJSON(priority uint8, value interface{}) (*PriorityItem, error) {
   151  	jsonBytes, err := f.EncodeJson(value)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return pq.Enqueue(priority, jsonBytes)
   157  }
   158  
   159  // Dequeue removes the next item in the priority queue and returns it.
   160  func (pq *PriorityQueue) Dequeue() (*PriorityItem, error) {
   161  	pq.Lock()
   162  	defer pq.Unlock()
   163  
   164  	// Check if queue is closed.
   165  	if !pq.isOpen {
   166  		return nil, ErrDBClosed
   167  	}
   168  
   169  	// Try to get the next item.
   170  	item, err := pq.getNextItem()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	// Remove this item from the priority queue.
   176  	if err = pq.db.Delete(item.Key, nil); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	// Increment head position.
   181  	pq.levels[pq.curLevel].head++
   182  
   183  	return item, nil
   184  }
   185  
   186  // DequeueByPriority removes the next item in the given priority level
   187  // and returns it.
   188  func (pq *PriorityQueue) DequeueByPriority(priority uint8) (*PriorityItem, error) {
   189  	pq.Lock()
   190  	defer pq.Unlock()
   191  
   192  	// Check if queue is closed.
   193  	if !pq.isOpen {
   194  		return nil, ErrDBClosed
   195  	}
   196  
   197  	// Try to get the next item in the given priority level.
   198  	item, err := pq.getItemByPriorityID(priority, pq.levels[priority].head+1)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	// Remove this item from the priority queue.
   204  	if err = pq.db.Delete(item.Key, nil); err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	// Increment head position.
   209  	pq.levels[priority].head++
   210  
   211  	return item, nil
   212  }
   213  
   214  // Peek returns the next item in the priority queue without removing it.
   215  func (pq *PriorityQueue) Peek() (*PriorityItem, error) {
   216  	pq.RLock()
   217  	defer pq.RUnlock()
   218  
   219  	// Check if queue is closed.
   220  	if !pq.isOpen {
   221  		return nil, ErrDBClosed
   222  	}
   223  
   224  	return pq.getNextItem()
   225  }
   226  
   227  // PeekByOffset returns the item located at the given offset,
   228  // starting from the head of the queue, without removing it.
   229  func (pq *PriorityQueue) PeekByOffset(offset uint64) (*PriorityItem, error) {
   230  	pq.RLock()
   231  	defer pq.RUnlock()
   232  
   233  	// Check if queue is closed.
   234  	if !pq.isOpen {
   235  		return nil, ErrDBClosed
   236  	}
   237  
   238  	// Check if queue is empty.
   239  	if pq.Length() == 0 {
   240  		return nil, ErrEmpty
   241  	}
   242  
   243  	// If the offset is within the current priority level.
   244  	if pq.levels[pq.curLevel].length() >= offset+1 {
   245  		return pq.getItemByPriorityID(pq.curLevel, pq.levels[pq.curLevel].head+offset+1)
   246  	}
   247  
   248  	return pq.findOffset(offset)
   249  }
   250  
   251  // PeekByPriorityID returns the item with the given ID and priority without
   252  // removing it.
   253  func (pq *PriorityQueue) PeekByPriorityID(priority uint8, id uint64) (*PriorityItem, error) {
   254  	pq.RLock()
   255  	defer pq.RUnlock()
   256  
   257  	// Check if queue is closed.
   258  	if !pq.isOpen {
   259  		return nil, ErrDBClosed
   260  	}
   261  
   262  	return pq.getItemByPriorityID(priority, id)
   263  }
   264  
   265  // Update updates an item in the priority queue without changing its
   266  // position.
   267  func (pq *PriorityQueue) Update(priority uint8, id uint64, newValue []byte) (*PriorityItem, error) {
   268  	pq.Lock()
   269  	defer pq.Unlock()
   270  
   271  	// Check if queue is closed.
   272  	if !pq.isOpen {
   273  		return nil, ErrDBClosed
   274  	}
   275  
   276  	// Check if item exists in queue.
   277  	if id <= pq.levels[priority].head || id > pq.levels[priority].tail {
   278  		return nil, ErrOutOfBounds
   279  	}
   280  
   281  	// Create new PriorityItem.
   282  	item := &PriorityItem{
   283  		ID:       id,
   284  		Priority: priority,
   285  		Key:      pq.generateKey(priority, id),
   286  		Value:    newValue,
   287  	}
   288  
   289  	// Update this item in the queue.
   290  	if err := pq.db.Put(item.Key, item.Value, nil); err != nil {
   291  		return nil, err
   292  	}
   293  
   294  	return item, nil
   295  }
   296  
   297  // UpdateString is a helper function for Update that accepts a value
   298  // as a string rather than a byte slice.
   299  func (pq *PriorityQueue) UpdateString(priority uint8, id uint64, newValue string) (*PriorityItem, error) {
   300  	return pq.Update(priority, id, []byte(newValue))
   301  }
   302  
   303  // UpdateObject is a helper function for Update that accepts any
   304  // value type, which is then encoded into a byte slice using
   305  // encoding/gob.
   306  //
   307  // Objects containing pointers with zero values will decode to nil
   308  // when using this function. This is due to how the encoding/gob
   309  // package works. Because of this, you should only use this function
   310  // to encode simple types.
   311  func (pq *PriorityQueue) UpdateObject(priority uint8, id uint64, newValue interface{}) (*PriorityItem, error) {
   312  	var buffer bytes.Buffer
   313  	enc := gob.NewEncoder(&buffer)
   314  	if err := enc.Encode(newValue); err != nil {
   315  		return nil, err
   316  	}
   317  	return pq.Update(priority, id, buffer.Bytes())
   318  }
   319  
   320  // UpdateObjectAsJSON is a helper function for Update that accepts
   321  // any value type, which is then encoded into a JSON byte slice using
   322  // encoding/json.
   323  //
   324  // Use this function to handle encoding of complex types.
   325  func (pq *PriorityQueue) UpdateObjectAsJSON(priority uint8, id uint64, newValue interface{}) (*PriorityItem, error) {
   326  	jsonBytes, err := f.EncodeJson(newValue)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	return pq.Update(priority, id, jsonBytes)
   332  }
   333  
   334  // Length returns the total number of items in the priority queue.
   335  func (pq *PriorityQueue) Length() uint64 {
   336  	pq.RLock()
   337  	defer pq.RUnlock()
   338  
   339  	var length uint64
   340  	for _, v := range pq.levels {
   341  		length += v.length()
   342  	}
   343  
   344  	return length
   345  }
   346  
   347  // Close closes the LevelDB database of the priority queue.
   348  func (pq *PriorityQueue) Close() error {
   349  	pq.Lock()
   350  	defer pq.Unlock()
   351  
   352  	// Check if queue is already closed.
   353  	if !pq.isOpen {
   354  		return nil
   355  	}
   356  
   357  	// Close the LevelDB database.
   358  	if err := pq.db.Close(); err != nil {
   359  		return err
   360  	}
   361  
   362  	// Reset head and tail of each priority level
   363  	// and set isOpen to false.
   364  	for i := 0; i <= 255; i++ {
   365  		pq.levels[uint8(i)].head = 0
   366  		pq.levels[uint8(i)].tail = 0
   367  	}
   368  	pq.isOpen = false
   369  
   370  	return nil
   371  }
   372  
   373  // Drop closes and deletes the LevelDB database of the priority queue.
   374  func (pq *PriorityQueue) Drop() error {
   375  	if err := pq.Close(); err != nil {
   376  		return err
   377  	}
   378  
   379  	return os.RemoveAll(pq.DataDir)
   380  }
   381  
   382  // cmpAsc returns wehther the given priority level is higher than the
   383  // current priority level based on ascending order.
   384  func (pq *PriorityQueue) cmpAsc(priority uint8) bool {
   385  	return pq.order == ASC && priority < pq.curLevel
   386  }
   387  
   388  // cmpAsc returns wehther the given priority level is higher than the
   389  // current priority level based on descending order.
   390  func (pq *PriorityQueue) cmpDesc(priority uint8) bool {
   391  	return pq.order == DESC && priority > pq.curLevel
   392  }
   393  
   394  // resetCurrentLevel resets the current priority level of the queue
   395  // so the highest level can be found.
   396  func (pq *PriorityQueue) resetCurrentLevel() {
   397  	if pq.order == ASC {
   398  		pq.curLevel = 255
   399  	} else if pq.order == DESC {
   400  		pq.curLevel = 0
   401  	}
   402  }
   403  
   404  // findOffset finds the given offset from the current queue position
   405  // based on priority order.
   406  func (pq *PriorityQueue) findOffset(offset uint64) (*PriorityItem, error) {
   407  	var length uint64
   408  	var curLevel uint8 = pq.curLevel
   409  	var newLevel int
   410  
   411  	// Handle newLevel initialization for descending order.
   412  	if pq.order == DESC {
   413  		newLevel = 255
   414  	}
   415  
   416  	// For condition expression.
   417  	condExpr := func(level int) bool {
   418  		if pq.order == ASC {
   419  			return level <= 255
   420  		}
   421  		return level >= 0
   422  	}
   423  
   424  	// For loop expression.
   425  	loopExpr := func(level *int) {
   426  		if pq.order == ASC {
   427  			*level++
   428  		} else if pq.order == DESC {
   429  			*level--
   430  		}
   431  	}
   432  
   433  	// Level comparison.
   434  	cmpLevels := func(newLevel, curLevel uint8) bool {
   435  		if pq.order == ASC {
   436  			return newLevel >= curLevel
   437  		}
   438  		return newLevel <= curLevel
   439  	}
   440  
   441  	// Loop through the priority levels.
   442  	for ; condExpr(newLevel); loopExpr(&newLevel) {
   443  		// If this level is lower than the current level based on ordering and contains items.
   444  		if cmpLevels(uint8(newLevel), curLevel) && pq.levels[uint8(newLevel)].length() > 0 {
   445  			curLevel = uint8(newLevel)
   446  			newLength := pq.levels[curLevel].length()
   447  
   448  			// If the offset is within the current priority level.
   449  			if length+newLength >= offset+1 {
   450  				return pq.getItemByPriorityID(curLevel, offset-length+1)
   451  			}
   452  
   453  			length += newLength
   454  		}
   455  	}
   456  
   457  	return nil, ErrOutOfBounds
   458  }
   459  
   460  // getNextItem returns the next item in the priority queue, updating
   461  // the current priority level of the queue if necessary.
   462  func (pq *PriorityQueue) getNextItem() (*PriorityItem, error) {
   463  	// If the current priority level is empty.
   464  	if pq.levels[pq.curLevel].length() == 0 {
   465  		// SetHeader starting value for curLevel.
   466  		pq.resetCurrentLevel()
   467  
   468  		// Try to get the next priority level.
   469  		for i := 0; i <= 255; i++ {
   470  			if (pq.cmpAsc(uint8(i)) || pq.cmpDesc(uint8(i))) && pq.levels[uint8(i)].length() > 0 {
   471  				pq.curLevel = uint8(i)
   472  			}
   473  		}
   474  
   475  		// If still empty, return queue empty error.
   476  		if pq.levels[pq.curLevel].length() == 0 {
   477  			return nil, ErrEmpty
   478  		}
   479  	}
   480  
   481  	// Try to get the next item in the current priority level.
   482  	return pq.getItemByPriorityID(pq.curLevel, pq.levels[pq.curLevel].head+1)
   483  }
   484  
   485  // getItemByID returns an item, if found, for the given ID.
   486  func (pq *PriorityQueue) getItemByPriorityID(priority uint8, id uint64) (*PriorityItem, error) {
   487  	// Check if empty or out of bounds.
   488  	if pq.levels[priority].length() == 0 {
   489  		return nil, ErrEmpty
   490  	} else if id <= pq.levels[priority].head || id > pq.levels[priority].tail {
   491  		return nil, ErrOutOfBounds
   492  	}
   493  
   494  	// GetHeader item from database.
   495  	var err error
   496  	item := &PriorityItem{ID: id, Priority: priority, Key: pq.generateKey(priority, id)}
   497  	if item.Value, err = pq.db.Get(item.Key, nil); err != nil {
   498  		return nil, err
   499  	}
   500  
   501  	return item, nil
   502  }
   503  
   504  // generatePrefix creates the key prefix for the given priority level.
   505  func (pq *PriorityQueue) generatePrefix(level uint8) []byte {
   506  	// priority + prefixSep = 1 + 1 = 2
   507  	prefix := make([]byte, 2)
   508  	prefix[0] = byte(level)
   509  	prefix[1] = prefixSep[0]
   510  	return prefix
   511  }
   512  
   513  // generateKey create a key to be used with LevelDB.
   514  func (pq *PriorityQueue) generateKey(priority uint8, id uint64) []byte {
   515  	// prefix + key = 2 + 8 = 10
   516  	key := make([]byte, 10)
   517  	copy(key[0:2], pq.generatePrefix(priority))
   518  	copy(key[2:], idToKey(id))
   519  	return key
   520  }
   521  
   522  // init initializes the priority queue data.
   523  func (pq *PriorityQueue) init() error {
   524  	// SetHeader starting value for curLevel.
   525  	pq.resetCurrentLevel()
   526  
   527  	// Loop through each priority level.
   528  	for i := 0; i <= 255; i++ {
   529  		// Create a new LevelDB Iterator for this priority level.
   530  		prefix := pq.generatePrefix(uint8(i))
   531  		iter := pq.db.NewIterator(util.BytesPrefix(prefix), nil)
   532  
   533  		// Create a new priorityLevel.
   534  		pl := &priorityLevel{
   535  			head: 0,
   536  			tail: 0,
   537  		}
   538  
   539  		// SetHeader priority level head to the first item.
   540  		if iter.First() {
   541  			pl.head = keyToID(iter.Key()[2:]) - 1
   542  
   543  			// Since this priority level has item(s), handle updating curLevel.
   544  			if pq.cmpAsc(uint8(i)) || pq.cmpDesc(uint8(i)) {
   545  				pq.curLevel = uint8(i)
   546  			}
   547  		}
   548  
   549  		// SetHeader priority level tail to the last item.
   550  		if iter.Last() {
   551  			pl.tail = keyToID(iter.Key()[2:])
   552  		}
   553  
   554  		if iter.Error() != nil {
   555  			return iter.Error()
   556  		}
   557  
   558  		pq.levels[i] = pl
   559  		iter.Release()
   560  	}
   561  
   562  	return nil
   563  }