Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
4.2 KiB
214 lines
4.2 KiB
// Copyright 2019 Lunny Xiao. All rights reserved. |
|
// Use of this source code is governed by a MIT-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package levelqueue |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
"sync" |
|
|
|
"github.com/syndtr/goleveldb/leveldb" |
|
) |
|
|
|
// Queue defines a queue struct |
|
type Queue struct { |
|
db *leveldb.DB |
|
highLock sync.Mutex |
|
lowLock sync.Mutex |
|
low int64 |
|
high int64 |
|
} |
|
|
|
// Open opens a queue object or create it if not exist |
|
func Open(dataDir string) (*Queue, error) { |
|
db, err := leveldb.OpenFile(dataDir, nil) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var queue = &Queue{ |
|
db: db, |
|
} |
|
queue.low, err = queue.readID(lowKey) |
|
if err == leveldb.ErrNotFound { |
|
queue.low = 1 |
|
err = db.Put(lowKey, id2bytes(1), nil) |
|
} |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
queue.high, err = queue.readID(highKey) |
|
if err == leveldb.ErrNotFound { |
|
err = db.Put(highKey, id2bytes(0), nil) |
|
} |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return queue, nil |
|
} |
|
|
|
func (queue *Queue) readID(key []byte) (int64, error) { |
|
bs, err := queue.db.Get(key, nil) |
|
if err != nil { |
|
return 0, err |
|
} |
|
return bytes2id(bs) |
|
} |
|
|
|
var ( |
|
lowKey = []byte("low") |
|
highKey = []byte("high") |
|
) |
|
|
|
func (queue *Queue) highincrement() (int64, error) { |
|
id := queue.high + 1 |
|
queue.high = id |
|
err := queue.db.Put(highKey, id2bytes(queue.high), nil) |
|
if err != nil { |
|
queue.high = queue.high - 1 |
|
return 0, err |
|
} |
|
return id, nil |
|
} |
|
|
|
func (queue *Queue) highdecrement() (int64, error) { |
|
queue.high = queue.high - 1 |
|
err := queue.db.Put(highKey, id2bytes(queue.high), nil) |
|
if err != nil { |
|
queue.high = queue.high + 1 |
|
return 0, err |
|
} |
|
return queue.high, nil |
|
} |
|
|
|
func (queue *Queue) lowincrement() (int64, error) { |
|
queue.low = queue.low + 1 |
|
err := queue.db.Put(lowKey, id2bytes(queue.low), nil) |
|
if err != nil { |
|
queue.low = queue.low - 1 |
|
return 0, err |
|
} |
|
return queue.low, nil |
|
} |
|
|
|
func (queue *Queue) lowdecrement() (int64, error) { |
|
queue.low = queue.low - 1 |
|
err := queue.db.Put(lowKey, id2bytes(queue.low), nil) |
|
if err != nil { |
|
queue.low = queue.low + 1 |
|
return 0, err |
|
} |
|
return queue.low, nil |
|
} |
|
|
|
// Len returns the length of the queue |
|
func (queue *Queue) Len() int64 { |
|
queue.lowLock.Lock() |
|
queue.highLock.Lock() |
|
l := queue.high - queue.low + 1 |
|
queue.highLock.Unlock() |
|
queue.lowLock.Unlock() |
|
return l |
|
} |
|
|
|
func id2bytes(id int64) []byte { |
|
var buf = make([]byte, 8) |
|
binary.PutVarint(buf, id) |
|
return buf |
|
} |
|
|
|
func bytes2id(b []byte) (int64, error) { |
|
return binary.ReadVarint(bytes.NewReader(b)) |
|
} |
|
|
|
// RPush pushes a data from right of queue |
|
func (queue *Queue) RPush(data []byte) error { |
|
queue.highLock.Lock() |
|
id, err := queue.highincrement() |
|
if err != nil { |
|
queue.highLock.Unlock() |
|
return err |
|
} |
|
err = queue.db.Put(id2bytes(id), data, nil) |
|
queue.highLock.Unlock() |
|
return err |
|
} |
|
|
|
// LPush pushes a data from left of queue |
|
func (queue *Queue) LPush(data []byte) error { |
|
queue.highLock.Lock() |
|
id, err := queue.lowdecrement() |
|
if err != nil { |
|
queue.highLock.Unlock() |
|
return err |
|
} |
|
err = queue.db.Put(id2bytes(id), data, nil) |
|
queue.highLock.Unlock() |
|
return err |
|
} |
|
|
|
// RPop pop a data from right of queue |
|
func (queue *Queue) RPop() ([]byte, error) { |
|
queue.highLock.Lock() |
|
currentID := queue.high |
|
|
|
res, err := queue.db.Get(id2bytes(currentID), nil) |
|
if err != nil { |
|
queue.highLock.Unlock() |
|
if err == leveldb.ErrNotFound { |
|
return nil, ErrNotFound |
|
} |
|
return nil, err |
|
} |
|
|
|
_, err = queue.highdecrement() |
|
if err != nil { |
|
queue.highLock.Unlock() |
|
return nil, err |
|
} |
|
|
|
err = queue.db.Delete(id2bytes(currentID), nil) |
|
queue.highLock.Unlock() |
|
if err != nil { |
|
return nil, err |
|
} |
|
return res, nil |
|
} |
|
|
|
// LPop pop a data from left of queue |
|
func (queue *Queue) LPop() ([]byte, error) { |
|
queue.lowLock.Lock() |
|
currentID := queue.low |
|
|
|
res, err := queue.db.Get(id2bytes(currentID), nil) |
|
if err != nil { |
|
queue.lowLock.Unlock() |
|
if err == leveldb.ErrNotFound { |
|
return nil, ErrNotFound |
|
} |
|
return nil, err |
|
} |
|
|
|
_, err = queue.lowincrement() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
err = queue.db.Delete(id2bytes(currentID), nil) |
|
queue.lowLock.Unlock() |
|
if err != nil { |
|
return nil, err |
|
} |
|
return res, nil |
|
} |
|
|
|
// Close closes the queue |
|
func (queue *Queue) Close() error { |
|
err := queue.db.Close() |
|
queue.db = nil |
|
return err |
|
}
|
|
|