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 }