Last active
February 25, 2025 08:39
-
-
Save suhodolskiy/0fa959c10fc744f43504e611c868e1dd to your computer and use it in GitHub Desktop.
Cache Loader
This file contains hidden or 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
| package cacheloader | |
| import ( | |
| "bytes" | |
| "context" | |
| "crypto/sha256" | |
| "encoding/gob" | |
| "fmt" | |
| "sync" | |
| "time" | |
| ) | |
| type CacheLoader[T any] struct { | |
| registry map[string][]chan T | |
| store Store[T] | |
| mu sync.Mutex | |
| tags []string | |
| } | |
| func New[T any](store Store[T], tags []string) *CacheLoader[T] { | |
| return &CacheLoader[T]{ | |
| registry: make(map[string][]chan T), | |
| mu: sync.Mutex{}, | |
| store: store, | |
| tags: tags, | |
| } | |
| } | |
| func (c *CacheLoader[T]) key(key any) string { | |
| if value, ok := key.(string); ok { | |
| return value | |
| } | |
| var b bytes.Buffer | |
| if err := gob.NewEncoder(&b).Encode(key); err != nil { | |
| panic(err) | |
| } | |
| hash := sha256.Sum256(b.Bytes()) | |
| return fmt.Sprintf("%x", hash) | |
| } | |
| func (c *CacheLoader[T]) Get(ctx context.Context, key any) (T, func()) { | |
| k := c.key(key) | |
| // Return the value if found in the store. | |
| if value, err := c.store.Get(ctx, k); err == nil { | |
| return value, nil | |
| } | |
| // Lock the mutex to access the registry. | |
| c.mu.Lock() | |
| if _, ok := c.registry[k]; ok { | |
| ch := make(chan T, 1) | |
| c.registry[k] = append(c.registry[k], ch) | |
| c.mu.Unlock() | |
| // Wait for the value to be set and return it. | |
| return <-ch, nil | |
| } else { | |
| // Initialize a new channel list if the key is not present. | |
| c.registry[k] = []chan T{} | |
| c.mu.Unlock() | |
| } | |
| var data T | |
| unlock := func() { | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| // Remove the key from the registry when done. | |
| delete(c.registry, k) | |
| } | |
| return data, unlock | |
| } | |
| func (c *CacheLoader[T]) Set(key any, value T) { | |
| k := c.key(key) | |
| c.mu.Lock() | |
| channels, exists := c.registry[k] | |
| c.mu.Unlock() | |
| if exists { | |
| for _, ch := range channels { | |
| // Send the value to waiting channels. | |
| select { | |
| case ch <- value: | |
| // Close the channel after sending the value. | |
| close(ch) | |
| default: | |
| } | |
| } | |
| } | |
| go func() { | |
| ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) | |
| defer cancel() | |
| if err := c.store.Set(ctx, k, value, c.tags); err != nil { | |
| // TODO: Log error | |
| } | |
| }() | |
| } | |
| func (c *CacheLoader[T]) Invalidate(ctx context.Context) error { | |
| return c.store.Invalidate(ctx, c.tags...) | |
| } |
This file contains hidden or 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
| package cacheloader | |
| import "context" | |
| type Store[T any] interface { | |
| Get(ctx context.Context, key string) (T, error) | |
| Set(ctx context.Context, key string, value any, tags []string) error | |
| Invalidate(ctx context.Context, tags ...string) error | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment