github.com/Jeffail/benthos/v3@v3.65.0/public/service/cache.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"time"
     7  
     8  	"github.com/Jeffail/benthos/v3/internal/component/cache"
     9  	"github.com/Jeffail/benthos/v3/internal/shutdown"
    10  	"github.com/Jeffail/benthos/v3/lib/metrics"
    11  	"github.com/Jeffail/benthos/v3/lib/types"
    12  )
    13  
    14  // Errors returned by cache types.
    15  var (
    16  	ErrKeyAlreadyExists = errors.New("key already exists")
    17  	ErrKeyNotFound      = errors.New("key does not exist")
    18  )
    19  
    20  // Cache is an interface implemented by Benthos caches.
    21  type Cache interface {
    22  	// Get a cache item.
    23  	Get(ctx context.Context, key string) ([]byte, error)
    24  
    25  	// Set a cache item, specifying an optional TTL. It is okay for caches to
    26  	// ignore the ttl parameter if it isn't possible to implement.
    27  	Set(ctx context.Context, key string, value []byte, ttl *time.Duration) error
    28  
    29  	// Add is the same operation as Set except that it returns an error if the
    30  	// key already exists. It is okay for caches to return nil on duplicates if
    31  	// it isn't possible to implement.
    32  	Add(ctx context.Context, key string, value []byte, ttl *time.Duration) error
    33  
    34  	// Delete attempts to remove a key. If the key does not exist then it is
    35  	// considered correct to return an error, however, for cache implementations
    36  	// where it is difficult to determine this then it is acceptable to return
    37  	// nil.
    38  	Delete(ctx context.Context, key string) error
    39  
    40  	Closer
    41  }
    42  
    43  // CacheItem represents an individual cache item.
    44  type CacheItem struct {
    45  	Key   string
    46  	Value []byte
    47  	TTL   *time.Duration
    48  }
    49  
    50  // batchedCache represents a cache where the underlying implementation is able
    51  // to benefit from batched set requests. This interface is optional for caches
    52  // and when implemented will automatically be utilised where possible.
    53  type batchedCache interface {
    54  	// SetMulti attempts to set multiple cache items in as few requests as
    55  	// possible.
    56  	SetMulti(ctx context.Context, keyValues ...CacheItem) error
    57  }
    58  
    59  //------------------------------------------------------------------------------
    60  
    61  // Implements types.Cache
    62  type airGapCache struct {
    63  	c  Cache
    64  	cm batchedCache
    65  
    66  	sig *shutdown.Signaller
    67  }
    68  
    69  func newAirGapCache(c Cache, stats metrics.Type) types.Cache {
    70  	ag := &airGapCache{c, nil, shutdown.NewSignaller()}
    71  	ag.cm, _ = c.(batchedCache)
    72  	return cache.NewV2ToV1Cache(ag, stats)
    73  }
    74  
    75  func (a *airGapCache) Get(ctx context.Context, key string) ([]byte, error) {
    76  	b, err := a.c.Get(ctx, key)
    77  	if errors.Is(err, ErrKeyNotFound) {
    78  		err = types.ErrKeyNotFound
    79  	}
    80  	return b, err
    81  }
    82  
    83  func (a *airGapCache) Set(ctx context.Context, key string, value []byte, ttl *time.Duration) error {
    84  	return a.c.Set(ctx, key, value, ttl)
    85  }
    86  
    87  func (a *airGapCache) SetMulti(ctx context.Context, keyValues map[string]types.CacheTTLItem) error {
    88  	if a.cm != nil {
    89  		items := make([]CacheItem, 0, len(keyValues))
    90  		for k, v := range keyValues {
    91  			items = append(items, CacheItem{
    92  				Key:   k,
    93  				Value: v.Value,
    94  				TTL:   v.TTL,
    95  			})
    96  		}
    97  		return a.cm.SetMulti(ctx, items...)
    98  	}
    99  	for k, v := range keyValues {
   100  		if err := a.c.Set(ctx, k, v.Value, v.TTL); err != nil {
   101  			return err
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  func (a *airGapCache) Add(ctx context.Context, key string, value []byte, ttl *time.Duration) error {
   108  	err := a.c.Add(ctx, key, value, ttl)
   109  	if errors.Is(err, ErrKeyAlreadyExists) {
   110  		err = types.ErrKeyAlreadyExists
   111  	}
   112  	return err
   113  }
   114  
   115  func (a *airGapCache) Delete(ctx context.Context, key string) error {
   116  	return a.c.Delete(ctx, key)
   117  }
   118  
   119  func (a *airGapCache) Close(ctx context.Context) error {
   120  	return a.c.Close(ctx)
   121  }
   122  
   123  //------------------------------------------------------------------------------
   124  
   125  // Implements Cache around a types.Cache
   126  type reverseAirGapCache struct {
   127  	c types.Cache
   128  }
   129  
   130  func newReverseAirGapCache(c types.Cache) *reverseAirGapCache {
   131  	return &reverseAirGapCache{c}
   132  }
   133  
   134  func (r *reverseAirGapCache) Get(ctx context.Context, key string) ([]byte, error) {
   135  	b, err := r.c.Get(key)
   136  	if errors.Is(err, types.ErrKeyNotFound) {
   137  		err = ErrKeyNotFound
   138  	}
   139  	return b, err
   140  }
   141  
   142  func (r *reverseAirGapCache) Set(ctx context.Context, key string, value []byte, ttl *time.Duration) error {
   143  	if cttl, ok := r.c.(types.CacheWithTTL); ok {
   144  		return cttl.SetWithTTL(key, value, ttl)
   145  	}
   146  	return r.c.Set(key, value)
   147  }
   148  
   149  func (r *reverseAirGapCache) Add(ctx context.Context, key string, value []byte, ttl *time.Duration) (err error) {
   150  	if cttl, ok := r.c.(types.CacheWithTTL); ok {
   151  		err = cttl.AddWithTTL(key, value, ttl)
   152  	} else {
   153  		err = r.c.Add(key, value)
   154  	}
   155  	if errors.Is(err, types.ErrKeyAlreadyExists) {
   156  		err = ErrKeyAlreadyExists
   157  	}
   158  	return
   159  }
   160  
   161  func (r *reverseAirGapCache) Delete(ctx context.Context, key string) error {
   162  	return r.c.Delete(key)
   163  }
   164  
   165  func (r *reverseAirGapCache) Close(ctx context.Context) error {
   166  	r.c.CloseAsync()
   167  	for {
   168  		// Gross but will do for now until we replace these with context params.
   169  		if err := r.c.WaitForClose(time.Millisecond * 100); err == nil {
   170  			return nil
   171  		}
   172  		select {
   173  		case <-ctx.Done():
   174  			return ctx.Err()
   175  		default:
   176  		}
   177  	}
   178  }