mirror of https://github.com/tidwall/tile38.git
expire package
This commit is contained in:
parent
4e8a7ccfcd
commit
3aa394219d
|
@ -0,0 +1,115 @@
|
|||
package expire
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Item is a something that can expire
|
||||
type Item interface {
|
||||
Expires() time.Time
|
||||
}
|
||||
|
||||
// List of expireable items
|
||||
type List struct {
|
||||
mu sync.Mutex
|
||||
queue queue
|
||||
bgrun bool
|
||||
arr []Item
|
||||
Expired func(item Item)
|
||||
}
|
||||
|
||||
// Push an item onto the queue
|
||||
func (list *List) Push(item Item) {
|
||||
unix := item.Expires().UnixNano()
|
||||
list.mu.Lock()
|
||||
if !list.bgrun {
|
||||
list.bgrun = true
|
||||
go list.bg()
|
||||
}
|
||||
list.queue.push(unix, item)
|
||||
list.mu.Unlock()
|
||||
}
|
||||
|
||||
func (list *List) bg() {
|
||||
unix := time.Now().UnixNano()
|
||||
for {
|
||||
list.mu.Lock()
|
||||
if list.queue.len == 0 {
|
||||
list.bgrun = false
|
||||
list.mu.Unlock()
|
||||
break
|
||||
}
|
||||
if unix > list.queue.peek().unix {
|
||||
n := list.queue.pop()
|
||||
list.mu.Unlock()
|
||||
if list.Expired != nil {
|
||||
list.Expired(n.item)
|
||||
}
|
||||
} else {
|
||||
list.mu.Unlock()
|
||||
time.Sleep(time.Second / 10)
|
||||
unix = time.Now().UnixNano()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type qnode struct {
|
||||
unix int64
|
||||
item Item
|
||||
}
|
||||
|
||||
type queue struct {
|
||||
nodes []qnode
|
||||
len int
|
||||
size int
|
||||
}
|
||||
|
||||
func (q *queue) push(unix int64, item Item) {
|
||||
if q.nodes == nil {
|
||||
q.nodes = make([]qnode, 2)
|
||||
} else {
|
||||
q.nodes = append(q.nodes, qnode{})
|
||||
}
|
||||
i := q.len + 1
|
||||
j := i / 2
|
||||
for i > 1 && q.nodes[j].unix > unix {
|
||||
q.nodes[i] = q.nodes[j]
|
||||
i = j
|
||||
j = j / 2
|
||||
}
|
||||
q.nodes[i].unix = unix
|
||||
q.nodes[i].item = item
|
||||
q.len++
|
||||
}
|
||||
|
||||
func (q *queue) peek() qnode {
|
||||
if q.len == 0 {
|
||||
return qnode{}
|
||||
}
|
||||
return q.nodes[1]
|
||||
}
|
||||
|
||||
func (q *queue) pop() qnode {
|
||||
if q.len == 0 {
|
||||
return qnode{}
|
||||
}
|
||||
n := q.nodes[1]
|
||||
q.nodes[1] = q.nodes[q.len]
|
||||
q.len--
|
||||
var j, k int
|
||||
i := 1
|
||||
for i != q.len+1 {
|
||||
k = q.len + 1
|
||||
j = 2 * i
|
||||
if j <= q.len && q.nodes[j].unix < q.nodes[k].unix {
|
||||
k = j
|
||||
}
|
||||
if j+1 <= q.len && q.nodes[j+1].unix < q.nodes[k].unix {
|
||||
k = j + 1
|
||||
}
|
||||
q.nodes[i] = q.nodes[k]
|
||||
i = k
|
||||
}
|
||||
return n
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package expire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
str string
|
||||
exp time.Time
|
||||
}
|
||||
|
||||
func (item *testItem) Expires() time.Time {
|
||||
return item.exp
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
var list List
|
||||
now := time.Now()
|
||||
list.Push(&testItem{"13", now.Add(13)})
|
||||
list.Push(&testItem{"11", now.Add(11)})
|
||||
list.Push(&testItem{"14", now.Add(14)})
|
||||
list.Push(&testItem{"10", now.Add(10)})
|
||||
list.Push(&testItem{"15", now.Add(15)})
|
||||
list.Push(&testItem{"12", now.Add(12)})
|
||||
|
||||
var lunix int64
|
||||
for list.queue.len > 0 {
|
||||
n2 := list.queue.pop()
|
||||
if n2.unix < lunix {
|
||||
t.Fatal("out of order")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomQueue(t *testing.T) {
|
||||
N := 100000
|
||||
now := time.Now()
|
||||
var list List
|
||||
for i := 0; i < N; i++ {
|
||||
list.Push(&testItem{fmt.Sprintf("%d", i),
|
||||
now.Add(time.Duration(rand.Float64() * float64(time.Second)))})
|
||||
}
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(N)
|
||||
// var items []Item
|
||||
// list.Expired = func(item Item) {
|
||||
// items = append(items, item)
|
||||
// wg.Done()
|
||||
// }
|
||||
|
||||
// wg.Wait()
|
||||
|
||||
var items []Item
|
||||
for list.queue.len > 0 {
|
||||
n1 := list.queue.peek()
|
||||
n2 := list.queue.pop()
|
||||
if n1 != n2 {
|
||||
t.Fatal("mismatch")
|
||||
}
|
||||
if n1.unix > n2.unix {
|
||||
t.Fatal("out of order")
|
||||
}
|
||||
items = append(items, n2.item)
|
||||
}
|
||||
|
||||
if !sort.SliceIsSorted(items, func(i, j int) bool {
|
||||
return items[i].Expires().Before(items[j].Expires())
|
||||
}) {
|
||||
t.Fatal("out of order")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExpires(t *testing.T) {
|
||||
N := 100000
|
||||
now := time.Now()
|
||||
var list List
|
||||
for i := 0; i < N; i++ {
|
||||
list.Push(&testItem{fmt.Sprintf("%d", i),
|
||||
now.Add(time.Duration(rand.Float64() * float64(time.Second)))})
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(N)
|
||||
var items []Item
|
||||
list.Expired = func(item Item) {
|
||||
items = append(items, item)
|
||||
wg.Done()
|
||||
}
|
||||
wg.Wait()
|
||||
if len(items) != N {
|
||||
t.Fatal("wrong result")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue