Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
  • Loading branch information
danclive committed Sep 11, 2022
0 parents commit 3a8fcc9
Show file tree
Hide file tree
Showing 6 changed files with 555 additions and 0 deletions.
163 changes: 163 additions & 0 deletions cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package cache

import (
"errors"
"sync"
"time"

"github.com/snple/types"
)

var (
ErrNotFound = errors.New("cache: key not found")
)

type Value[T any] struct {
Data T
TTL time.Duration
Updated time.Time
}

func newValue[T any](data T, ttl time.Duration) Value[T] {
return Value[T]{
Data: data,
TTL: ttl,
Updated: time.Now(),
}
}

func (v *Value[T]) Alive() bool {
return v != nil && (v.TTL == 0 || time.Since(v.Updated) <= v.TTL)
}

type Cache[T any] struct {
data map[string]Value[T]
lock sync.RWMutex
miss func(key string) (T, time.Duration, error)
}

func NewCache[T any](miss func(key string) (T, time.Duration, error)) *Cache[T] {
return &Cache[T]{
data: make(map[string]Value[T]),
lock: sync.RWMutex{},
miss: miss,
}
}

func (c *Cache[T]) GetValue(key string) types.Option[Value[T]] {
c.lock.RLock()
defer c.lock.RUnlock()

if value, ok := c.data[key]; ok {
return types.Some(value)
}

return types.None[Value[T]]()
}

func (c *Cache[T]) Get(key string) types.Option[T] {
c.lock.RLock()
defer c.lock.RUnlock()

if value, ok := c.data[key]; ok && value.Alive() {
return types.Some(value.Data)
}

return types.None[T]()
}

func (c *Cache[T]) GetWithMiss(key string) (types.Option[T], error) {
if v := c.Get(key); v.IsSome() {
return v, nil
}

if c.miss != nil {
value, ttl, err := c.miss(key)
if err != nil {
return types.None[T](), err
}

c.Set(key, value, ttl)
return types.Some(value), nil
}

return types.None[T](), ErrNotFound
}

func (c *Cache[T]) Set(key string, value T, ttl time.Duration) {
c.lock.Lock()
defer c.lock.Unlock()

c.data[key] = newValue(value, ttl)
}

func (c *Cache[T]) AutoSet(key string, fn func(key string) (T, time.Duration, error), duration time.Duration) chan<- struct{} {
quit := make(chan struct{})

go func() {
ticker := time.NewTicker(duration)
defer ticker.Stop()

for {
select {
case <-ticker.C:
if value, ttl, err := fn(key); err == nil {
c.Set(key, value, ttl)
}
case <-quit:
return
}
}
}()

return quit
}

func (c *Cache[T]) Delete(key string) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.data, key)
}

func (c *Cache[T]) DeleteAll() {
c.lock.Lock()
defer c.lock.Unlock()
c.data = make(map[string]Value[T])
}

func (c *Cache[T]) Size() int {
c.lock.RLock()
defer c.lock.RUnlock()
return len(c.data)
}

func (c *Cache[T]) GC() {
c.lock.Lock()
defer c.lock.Unlock()

for key, value := range c.data {
if !value.Alive() {
delete(c.data, key)
}
}
}

func (c *Cache[T]) AutoGC(duration time.Duration) chan<- struct{} {
quit := make(chan struct{})

go func() {
ticker := time.NewTicker(duration)
defer ticker.Stop()

for {
select {
case <-ticker.C:
c.GC()
case <-quit:
return
}
}
}()

return quit
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/snple/types

go 1.18
63 changes: 63 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package types

type Option[T any] struct {
value *T
}

func Some[T any](value T) Option[T] {
return Option[T]{
value: &value,
}
}

func None[T any]() Option[T] {
return Option[T]{
value: nil,
}
}

func (o *Option[T]) IsSome() bool {
return o.value != nil
}

func (o *Option[T]) IsNone() bool {
return o.value == nil
}

func (o *Option[T]) Unwrap() T {
if o.IsNone() {
panic("called `Option.Get()` on a `None` value")
}

return *o.value
}

func (o *Option[T]) Unchecked(msg string) *T {
return o.value
}

func (o *Option[T]) Take() Option[T] {
if o.IsNone() {
return Option[T]{
value: nil,
}
}

ret := Option[T]{
value: o.value,
}

o.value = nil

return ret
}

func (o *Option[T]) Replace(value T) Option[T] {
tmp := *o

o = &Option[T]{
value: o.value,
}

return tmp
}
37 changes: 37 additions & 0 deletions option_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package types

import "testing"

func TestOption(t *testing.T) {
if res := Some(123); res.IsSome() {
t.Log("the result is", res.Get())
} else {
panic("result should be some")
}

if res := None[int](); res.IsNone() {
t.Log("the result is none")
} else {
panic("result should be none")
}

if res := Some(123); res.IsSome() {
t.Log("the result is", res.Get())

take := res.Take()
t.Log("the result is", take.Get())
t.Log("the result is", res.IsNone())

} else {
panic("result should be some")
}

res := Some(456)
t.Log("the result is", res.Get())

res2 := res.Replace(789)
t.Log("the result is", res2.Get())
res2 = Some(789)
t.Log("the result is", res2.Get())
t.Log("the result is", res.Get())
}
Loading

0 comments on commit 3a8fcc9

Please sign in to comment.