forked from jellydator/ttlcache
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,290 additions
and
1,636 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package ttlcache | ||
|
||
import ( | ||
"container/heap" | ||
"container/list" | ||
) | ||
|
||
// expirationQueue stores items that are ordered by their expiration | ||
// timestamps. The 0th item is closest to its expiration. | ||
type expirationQueue[K comparable, V any] []*list.Element | ||
|
||
// newExpirationQueue creates and initializes a new expiration queue. | ||
func newExpirationQueue[K comparable, V any]() expirationQueue[K, V] { | ||
q := make(expirationQueue[K, V], 0) | ||
heap.Init(&q) | ||
return q | ||
} | ||
|
||
// isEmpty checks if the queue is empty. | ||
func (q expirationQueue[K, V]) isEmpty() bool { | ||
return q.Len() == 0 | ||
} | ||
|
||
// update updates an existing item's value and position in the queue. | ||
func (q *expirationQueue[K, V]) update(elem *list.Element) { | ||
heap.Fix(q, elem.Value.(*Item[K, V]).queueIndex) | ||
} | ||
|
||
// push pushes a new item into the queue and updates the order of its | ||
// elements. | ||
func (q *expirationQueue[K, V]) push(elem *list.Element) { | ||
heap.Push(q, elem) | ||
} | ||
|
||
// remove removes an item from the queue and updates the order of its | ||
// elements. | ||
func (q *expirationQueue[K, V]) remove(elem *list.Element) { | ||
heap.Remove(q, elem.Value.(*Item[K, V]).queueIndex) | ||
} | ||
|
||
// Len returns the total number of items in the queue. | ||
func (q expirationQueue[K, V]) Len() int { | ||
return len(q) | ||
} | ||
|
||
// Less checks if the item at the i position expires sooner than | ||
// the one at the j position. | ||
func (q expirationQueue[K, V]) Less(i, j int) bool { | ||
item1, item2 := q[i].Value.(*Item[K, V]), q[j].Value.(*Item[K, V]) | ||
if item1.expiresAt.IsZero() { | ||
return false | ||
} | ||
|
||
if item2.expiresAt.IsZero() { | ||
return true | ||
} | ||
|
||
return item1.expiresAt.Before(item2.expiresAt) | ||
} | ||
|
||
// Swap switches the places of two queue items. | ||
func (q expirationQueue[K, V]) Swap(i, j int) { | ||
q[i], q[j] = q[j], q[i] | ||
q[i].Value.(*Item[K, V]).queueIndex = i | ||
q[j].Value.(*Item[K, V]).queueIndex = j | ||
} | ||
|
||
// Push appends a new item to the item slice. | ||
func (q *expirationQueue[K, V]) Push(x interface{}) { | ||
elem := x.(*list.Element) | ||
elem.Value.(*Item[K, V]).queueIndex = len(*q) | ||
*q = append(*q, elem) | ||
} | ||
|
||
// Pop removes and returns the last item. | ||
func (q *expirationQueue[K, V]) Pop() interface{} { | ||
old := *q | ||
i := len(old) - 1 | ||
elem := old[i] | ||
elem.Value.(*Item[K, V]).queueIndex = -1 | ||
old[i] = nil // avoid memory leak | ||
*q = old[:i] | ||
|
||
return elem | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package ttlcache | ||
|
||
import ( | ||
"container/list" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_newExpirationQueue(t *testing.T) { | ||
assert.NotNil(t, newExpirationQueue[string, string]()) | ||
} | ||
|
||
func Test_expirationQueue_isEmpty(t *testing.T) { | ||
assert.True(t, (expirationQueue[string, string]{}).isEmpty()) | ||
assert.False(t, (expirationQueue[string, string]{{}}).isEmpty()) | ||
} | ||
|
||
func Test_expirationQueue_update(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
}, | ||
} | ||
|
||
q.update(q[1]) | ||
require.Len(t, q, 2) | ||
assert.Equal(t, "test2", q[0].Value.(*Item[string, string]).value) | ||
} | ||
|
||
func Test_expirationQueue_push(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
} | ||
elem := &list.Element{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
} | ||
|
||
q.push(elem) | ||
require.Len(t, q, 2) | ||
assert.Equal(t, "test2", q[0].Value.(*Item[string, string]).value) | ||
} | ||
|
||
func Test_expirationQueue_remove(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
}, | ||
} | ||
|
||
q.remove(q[1]) | ||
require.Len(t, q, 1) | ||
assert.Equal(t, "test1", q[0].Value.(*Item[string, string]).value) | ||
} | ||
|
||
func Test_expirationQueue_Len(t *testing.T) { | ||
assert.Equal(t, 1, (expirationQueue[string, string]{{}}).Len()) | ||
} | ||
|
||
func Test_expirationQueue_Less(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test3", | ||
queueIndex: 2, | ||
}, | ||
}, | ||
} | ||
|
||
assert.False(t, q.Less(2, 1)) | ||
assert.True(t, q.Less(1, 2)) | ||
assert.True(t, q.Less(1, 0)) | ||
assert.False(t, q.Less(0, 1)) | ||
} | ||
|
||
func Test_expirationQueue_Swap(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
}, | ||
} | ||
|
||
q.Swap(0, 1) | ||
assert.Equal(t, "test2", q[0].Value.(*Item[string, string]).value) | ||
assert.Equal(t, "test1", q[1].Value.(*Item[string, string]).value) | ||
} | ||
|
||
func Test_expirationQueue_Push(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
} | ||
|
||
elem := &list.Element{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
} | ||
|
||
q.Push(elem) | ||
require.Len(t, q, 2) | ||
assert.Equal(t, "test2", q[1].Value.(*Item[string, string]).value) | ||
} | ||
|
||
func Test_expirationQueue_Pop(t *testing.T) { | ||
q := expirationQueue[string, string]{ | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test1", | ||
queueIndex: 0, | ||
expiresAt: time.Now().Add(time.Hour), | ||
}, | ||
}, | ||
{ | ||
Value: &Item[string, string]{ | ||
value: "test2", | ||
queueIndex: 1, | ||
expiresAt: time.Now().Add(time.Minute), | ||
}, | ||
}, | ||
} | ||
|
||
v := q.Pop() | ||
require.NotNil(t, v) | ||
assert.Equal(t, "test2", v.(*list.Element).Value.(*Item[string, string]).value) | ||
require.Len(t, q, 1) | ||
assert.Equal(t, "test1", q[0].Value.(*Item[string, string]).value) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.