Skip to content

Commit

Permalink
go 1.23 and iterator support
Browse files Browse the repository at this point in the history
  • Loading branch information
webermarci committed Aug 20, 2024
1 parent 3c9533a commit 096740f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publishing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22
go-version: 1.23

- name: Checkout
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22
go-version: 1.23

- name: Checkout
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/webermarci/pantry

go 1.22
go 1.23
106 changes: 65 additions & 41 deletions pantry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ package pantry

import (
"context"
"iter"
"sync"
"time"
)

type Item[T any] struct {
Value T
Expires int64
type item[T any] struct {
value T
expires int64
}

type Pantry[T any] struct {
expiration time.Duration
store map[string]Item[T]
store map[string]item[T]
mutex sync.RWMutex
}

Expand All @@ -22,47 +23,19 @@ func (pantry *Pantry[T]) Get(key string) (T, bool) {
defer pantry.mutex.RUnlock()

item, found := pantry.store[key]
if found && time.Now().UnixNano() > item.Expires {
if found && time.Now().UnixNano() > item.expires {
return *new(T), false
}
return item.Value, found
}

func (pantry *Pantry[T]) GetAll() map[string]T {
pantry.mutex.RLock()
defer pantry.mutex.RUnlock()

items := make(map[string]T)
for key, item := range pantry.store {
if time.Now().UnixNano() > item.Expires {
continue
}
items[key] = item.Value
}
return items
}

func (pantry *Pantry[T]) GetAllFlat() []T {
pantry.mutex.RLock()
defer pantry.mutex.RUnlock()

items := []T{}
for _, item := range pantry.store {
if time.Now().UnixNano() > item.Expires {
continue
}
items = append(items, item.Value)
}
return items
return item.value, found
}

func (pantry *Pantry[T]) Set(key string, value T) {
pantry.mutex.Lock()
defer pantry.mutex.Unlock()

pantry.store[key] = Item[T]{
Value: value,
Expires: time.Now().Add(pantry.expiration).UnixNano(),
pantry.store[key] = item[T]{
value: value,
expires: time.Now().Add(pantry.expiration).UnixNano(),
}
}

Expand All @@ -80,31 +53,82 @@ func (pantry *Pantry[T]) IsEmpty() bool {
return len(pantry.store) == 0
}

func (pantry *Pantry[T]) Keys() iter.Seq[string] {
return func(yield func(string) bool) {
pantry.mutex.RLock()
defer pantry.mutex.RUnlock()

for key, item := range pantry.store {
if time.Now().UnixNano() > item.expires {
continue
}

if !yield(key) {
return
}
}
}
}

func (pantry *Pantry[T]) Values() iter.Seq[T] {
return func(yield func(T) bool) {
pantry.mutex.RLock()
defer pantry.mutex.RUnlock()

for _, item := range pantry.store {
if time.Now().UnixNano() > item.expires {
continue
}

if !yield(item.value) {
return
}
}
}
}

func (pantry *Pantry[T]) All() iter.Seq2[string, T] {
return func(yield func(string, T) bool) {
pantry.mutex.RLock()
defer pantry.mutex.RUnlock()

for key, item := range pantry.store {
if time.Now().UnixNano() > item.expires {
continue
}

if !yield(key, item.value) {
return
}
}
}
}

func New[T any](ctx context.Context, expiration time.Duration) *Pantry[T] {
pantry := &Pantry[T]{
expiration: expiration,
store: make(map[string]Item[T]),
store: make(map[string]item[T]),
mutex: sync.RWMutex{},
}

go func() {
ticker := time.NewTicker(3 * time.Second)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()

for {
select {
case <-ticker.C:
pantry.mutex.Lock()
for key, item := range pantry.store {
if time.Now().UnixNano() > item.Expires {
if time.Now().UnixNano() > item.expires {
delete(pantry.store, key)
}
}
pantry.mutex.Unlock()

case <-ctx.Done():
pantry.mutex.Lock()
pantry.store = make(map[string]Item[T])
pantry.store = make(map[string]item[T])
pantry.mutex.Unlock()
return
}
Expand Down
127 changes: 104 additions & 23 deletions pantry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestCleaning(t *testing.T) {
t.Fatal("not found")
}

time.Sleep(3 * time.Second)
time.Sleep(5 * time.Second)

if _, found := p.Get("test"); found {
t.Fatal("found")
Expand Down Expand Up @@ -102,74 +102,155 @@ func TestGetIgnoreExpired(t *testing.T) {
}
}

func TestGetAll(t *testing.T) {
func TestValues(t *testing.T) {
p := New[int](context.Background(), time.Hour)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

values := p.GetAll()
if len(values) != 3 {
t.Log(values)
counter := 0

for value := range p.Values() {
t.Log(value)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}
}

func TestValuesIgnoreExpired(t *testing.T) {
p := New[int](context.Background(), 10*time.Millisecond)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

counter := 0

for value := range p.Values() {
t.Log(value)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}

time.Sleep(20 * time.Millisecond)

counter = 0

for value := range p.Values() {
t.Log(value)
counter++
}

if counter != 0 {
t.Fatal("not ignored")
}
}

func TestKeys(t *testing.T) {
p := New[int](context.Background(), time.Hour)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

counter := 0

for key := range p.Keys() {
t.Log(key)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}
}

func TestGetAllIgnoreExpired(t *testing.T) {
func TestKeysIgnoreExpired(t *testing.T) {
p := New[int](context.Background(), 10*time.Millisecond)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

values := p.GetAll()
if len(values) != 3 {
t.Log(values)
counter := 0

for key := range p.Keys() {
t.Log(key)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}

time.Sleep(20 * time.Millisecond)

values = p.GetAll()
if len(values) != 0 {
t.Log(values)
counter = 0

for key := range p.Keys() {
t.Log(key)
counter++
}

if counter != 0 {
t.Fatal("not ignored")
}
}

func TestGetAllFlat(t *testing.T) {
func TestAll(t *testing.T) {
p := New[int](context.Background(), time.Hour)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

values := p.GetAllFlat()
if len(values) != 3 {
t.Log(values)
counter := 0

for key, value := range p.All() {
t.Log(key, value)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}
}

func TestGetAllFlatIgnoreExpired(t *testing.T) {
func TestAllIgnoreExpired(t *testing.T) {
p := New[int](context.Background(), 10*time.Millisecond)

p.Set("first", 1)
p.Set("second", 2)
p.Set("third", 3)

values := p.GetAllFlat()
if len(values) != 3 {
t.Log(values)
counter := 0

for key, value := range p.All() {
t.Log(key, value)
counter++
}

if counter != 3 {
t.Fatal("not 3 items")
}

time.Sleep(20 * time.Millisecond)

values = p.GetAllFlat()
if len(values) != 0 {
t.Log(values)
counter = 0

for key, value := range p.All() {
t.Log(key, value)
counter++
}

if counter != 0 {
t.Fatal("not ignored")
}
}
Expand Down

0 comments on commit 096740f

Please sign in to comment.