initial commit
Marco Andronaco andronacomarco@gmail.com
Fri, 14 Jun 2024 10:17:58 +0200
5 files changed,
215 insertions(+),
0 deletions(-)
A
LICENSE
@@ -0,0 +1,21 @@
+MIT License + +Copyright (c) 2024 Marco Andronaco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
A
README
@@ -0,0 +1,28 @@
+volatile +-------- + +A (dead) simple volatile data storage written in Go. + + +FEATURES + +• Automatic cleanup via goroutines! +• Get, Set, Has and Remove methods! +• Can be as fast (or slow) as you wish! +• Does not use any external libraries, only native go! +• Absolutely unsafe memory-wise and probably more resource-intensive than any other alternative. + + +USAGE + +Check out 'volatile_test.go' for some examples. + + +INSTALLING + +go get github.com/BiRabittoh/volatile + + +LICENSE + +volatile is licensed under MIT.
A
volatile.go
@@ -0,0 +1,80 @@
+package volatile + +import ( + "fmt" + "time" +) + +type Element[V any] struct { + value *V + Timestamp time.Time +} + +type Volatile[K comparable, V any] struct { + data map[K]Element[V] + timeToLive time.Duration +} + +func (v *Volatile[K, V]) clean() (count int) { + now := time.Now() + + for key, value := range v.data { + if now.Sub(value.Timestamp) > v.timeToLive { + delete(v.data, key) + count += 1 + } + } + return +} + +func (v *Volatile[K, V]) cleanupRoutine(interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for range ticker.C { + v.clean() + } +} + +func NewVolatile[K comparable, V any](timeToLive time.Duration, cleanupInterval time.Duration) *Volatile[K, V] { + v := &Volatile[K, V]{ + data: make(map[K]Element[V]), + timeToLive: timeToLive, + } + go v.cleanupRoutine(cleanupInterval) + return v +} + +func (v *Volatile[K, V]) Has(key K) (ok bool) { + v.clean() + _, ok = v.data[key] + return +} + +func (v *Volatile[K, V]) Get(key K) (*V, error) { + v.clean() + element, ok := v.data[key] + + if !ok { + return nil, fmt.Errorf("not found") + } + + return element.value, nil +} + +func (v *Volatile[K, V]) Remove(key K) (*V, error) { + v.clean() + value, ok := v.data[key] + + if !ok { + return nil, fmt.Errorf("not found") + } + + delete(v.data, key) + return value.value, nil +} + +func (v *Volatile[K, V]) Set(key K, value *V) { + v.data[key] = Element[V]{value: value, Timestamp: time.Now()} + v.clean() +}
A
volatile_test.go
@@ -0,0 +1,83 @@
+package volatile + +import ( + "testing" + "time" +) + +func TestVolatile_SetGet(t *testing.T) { + cache := NewVolatile[string, string](2*time.Second, 1*time.Second) + + key := "key1" + value := "value1" + cache.Set(key, &value) + + got, err := cache.Get(key) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if *got != value { + t.Errorf("got %v, want %v", *got, value) + } + + if !cache.Has(key) { + t.Errorf("expected key %v to exist", key) + } +} + +func TestVolatile_Remove(t *testing.T) { + cache := NewVolatile[string, string](2*time.Second, 1*time.Second) + + key := "key1" + value := "value1" + cache.Set(key, &value) + + got, err := cache.Remove(key) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if *got != value { + t.Errorf("got %v, want %v", *got, value) + } + + if cache.Has(key) { + t.Errorf("expected key %v to be removed", key) + } +} + +func TestVolatile_Clean(t *testing.T) { + cache := NewVolatile[string, string](100*time.Millisecond, 50*time.Millisecond) + + key := "key1" + value := "value1" + cache.Set(key, &value) + + time.Sleep(150 * time.Millisecond) // Wait for the element to expire + + if cache.Has(key) { + t.Errorf("expected key %v to be expired and removed", key) + } + + _, err := cache.Get(key) + if err == nil { + t.Errorf("expected error when getting expired key %v", key) + } +} + +func TestVolatile_AutomaticCleanup(t *testing.T) { + cache := NewVolatile[string, string](100*time.Millisecond, 50*time.Millisecond) + + key1 := "key1" + value1 := "value1" + cache.Set(key1, &value1) + + key2 := "key2" + value2 := "value2" + cache.Set(key2, &value2) + + time.Sleep(150 * time.Millisecond) // Wait for the elements to expire + + if cache.Has(key1) || cache.Has(key2) { + t.Errorf("expected all keys to be expired and removed") + } +}