github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/shareds/store.go (about)

     1  /*
     2   * Copyright 2023 Wang Min Xiang
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * 	http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package shareds
    19  
    20  import (
    21  	"github.com/aacfactory/configures"
    22  	"github.com/aacfactory/errors"
    23  	"github.com/aacfactory/fns/commons/bytex"
    24  	"github.com/aacfactory/fns/context"
    25  	"github.com/aacfactory/logs"
    26  	"github.com/tidwall/btree"
    27  	"strconv"
    28  	"sync"
    29  	"sync/atomic"
    30  	"time"
    31  )
    32  
    33  type Store interface {
    34  	Get(ctx context.Context, key []byte) (value []byte, has bool, err error)
    35  	Set(ctx context.Context, key []byte, value []byte) (err error)
    36  	SetWithTTL(ctx context.Context, key []byte, value []byte, ttl time.Duration) (err error)
    37  	Remove(ctx context.Context, key []byte) (err error)
    38  	Incr(ctx context.Context, key []byte, delta int64) (v int64, err error)
    39  	Expire(ctx context.Context, key []byte, ttl time.Duration) (err error)
    40  	Close()
    41  }
    42  
    43  type StoreBuilder interface {
    44  	Build(ctx context.Context, config configures.Config) (store Store, err error)
    45  }
    46  
    47  var (
    48  	localStoreBuilder LocalStoreBuild = defaultLocalStoreBuild
    49  )
    50  
    51  func RegisterLocalStoreBuild(build LocalStoreBuild) {
    52  	localStoreBuilder = build
    53  }
    54  
    55  type LocalStoreBuild func(log logs.Logger, config configures.Config) (Store, error)
    56  
    57  type DefaultLocalSharedStoreConfig struct {
    58  	ShrinkDuration time.Duration `json:"shrinkDuration"`
    59  }
    60  
    61  func defaultLocalStoreBuild(log logs.Logger, config configures.Config) (store Store, err error) {
    62  	cfg := DefaultLocalSharedStoreConfig{}
    63  	cfgErr := config.As(&cfg)
    64  	if cfgErr != nil {
    65  		err = errors.Warning("fns: build default local shared store failed").WithCause(cfgErr)
    66  		return
    67  	}
    68  	shrinkDuration := cfg.ShrinkDuration
    69  	if shrinkDuration == 0 {
    70  		shrinkDuration = 1 * time.Hour
    71  	}
    72  	s := &localStore{
    73  		log:            log,
    74  		shrinkDuration: shrinkDuration,
    75  		closeCh:        make(chan struct{}, 1),
    76  		locker:         sync.RWMutex{},
    77  		values:         btree.NewMap[string, Entry](0),
    78  	}
    79  	if shrinkDuration > 0 {
    80  		go s.shrink()
    81  	}
    82  	store = s
    83  	return
    84  }
    85  
    86  type localStore struct {
    87  	log            logs.Logger
    88  	shrinkDuration time.Duration
    89  	closeCh        chan struct{}
    90  	locker         sync.RWMutex
    91  	values         *btree.Map[string, Entry]
    92  }
    93  
    94  func (store *localStore) Get(_ context.Context, key []byte) (value []byte, has bool, err error) {
    95  	if key == nil || len(key) == 0 {
    96  		err = errors.Warning("fns: shared store get failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
    97  		return
    98  	}
    99  	store.locker.RLock()
   100  	defer store.locker.RUnlock()
   101  	sk := bytex.ToString(key)
   102  	entry, exist := store.values.Get(sk)
   103  	if !exist {
   104  		return
   105  	}
   106  	if entry.Expired() {
   107  		return
   108  	}
   109  	has = true
   110  	switch v := entry.Value.(type) {
   111  	case []byte:
   112  		value = v
   113  		break
   114  	case *atomic.Int64:
   115  		value = bytex.FromString(strconv.FormatInt(v.Load(), 64))
   116  		break
   117  	default:
   118  		has = false
   119  		break
   120  	}
   121  	return
   122  }
   123  
   124  func (store *localStore) Set(_ context.Context, key []byte, value []byte) (err error) {
   125  	if key == nil || len(key) == 0 {
   126  		err = errors.Warning("fns: shared store set failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
   127  		return
   128  	}
   129  	store.locker.Lock()
   130  	defer store.locker.Unlock()
   131  	sk := bytex.ToString(key)
   132  	store.values.Set(sk, Entry{
   133  		key:      sk,
   134  		Value:    value,
   135  		Deadline: time.Time{},
   136  	})
   137  	return
   138  }
   139  
   140  func (store *localStore) SetWithTTL(ctx context.Context, key []byte, value []byte, ttl time.Duration) (err error) {
   141  	if key == nil || len(key) == 0 {
   142  		err = errors.Warning("fns: shared store set failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
   143  		return
   144  	}
   145  	if ttl < 1 {
   146  		err = store.Set(ctx, key, value)
   147  		return
   148  	}
   149  	store.locker.Lock()
   150  	defer store.locker.Unlock()
   151  	sk := bytex.ToString(key)
   152  	store.values.Set(sk, Entry{
   153  		key:      sk,
   154  		Value:    value,
   155  		Deadline: time.Now().Add(ttl),
   156  	})
   157  	return
   158  }
   159  
   160  func (store *localStore) Incr(_ context.Context, key []byte, delta int64) (v int64, err error) {
   161  	if key == nil || len(key) == 0 {
   162  		err = errors.Warning("fns: shared store incr failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
   163  		return
   164  	}
   165  	store.locker.Lock()
   166  	defer store.locker.Unlock()
   167  	sk := bytex.ToString(key)
   168  	var n *atomic.Int64
   169  	entry, exist := store.values.Get(sk)
   170  	if exist {
   171  		if entry.Expired() {
   172  			n = new(atomic.Int64)
   173  		} else {
   174  			nn, ok := entry.Value.(*atomic.Int64)
   175  			if !ok {
   176  				err = errors.Warning("fns: shared store incr failed").WithCause(errors.Warning("value of key is not int64")).WithMeta("shared", "local").WithMeta("key", string(key))
   177  				return
   178  			}
   179  			n = nn
   180  		}
   181  	} else {
   182  		n = new(atomic.Int64)
   183  		entry = Entry{
   184  			key:      sk,
   185  			Value:    n,
   186  			Deadline: time.Time{},
   187  		}
   188  	}
   189  	v = n.Add(delta)
   190  	store.values.Set(sk, entry)
   191  	return
   192  }
   193  
   194  func (store *localStore) Remove(_ context.Context, key []byte) (err error) {
   195  	if key == nil || len(key) == 0 {
   196  		err = errors.Warning("fns: shared store remove failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
   197  		return
   198  	}
   199  	store.locker.Lock()
   200  	defer store.locker.Unlock()
   201  	sk := bytex.ToString(key)
   202  	store.values.Delete(sk)
   203  	return
   204  }
   205  
   206  func (store *localStore) Expire(ctx context.Context, key []byte, ttl time.Duration) (err error) {
   207  	if key == nil || len(key) == 0 {
   208  		err = errors.Warning("fns: shared store expire key failed").WithCause(errors.Warning("key is required")).WithMeta("shared", "local").WithMeta("key", string(key))
   209  		return
   210  	}
   211  	store.locker.Lock()
   212  	defer store.locker.Unlock()
   213  	sk := bytex.ToString(key)
   214  	entry, exist := store.values.Get(sk)
   215  	if !exist {
   216  		return
   217  	}
   218  	if ttl < 1 {
   219  		entry.Deadline = time.Time{}
   220  	} else {
   221  		entry.Deadline = time.Now().Add(ttl)
   222  	}
   223  	store.values.Set(sk, entry)
   224  	return
   225  }
   226  
   227  func (store *localStore) shrink() {
   228  	timer := time.NewTimer(store.shrinkDuration)
   229  	stop := false
   230  	for {
   231  		select {
   232  		case <-store.closeCh:
   233  			stop = true
   234  			break
   235  		case <-timer.C:
   236  			store.locker.Lock()
   237  			expires := make([]string, 0, 1)
   238  			values := store.values.Values()
   239  			for _, value := range values {
   240  				if value.Expired() {
   241  					expires = append(expires, value.key)
   242  				}
   243  			}
   244  			for _, expire := range expires {
   245  				store.values.Delete(expire)
   246  			}
   247  			store.locker.Unlock()
   248  			timer.Reset(store.shrinkDuration)
   249  			break
   250  		}
   251  		if stop {
   252  			break
   253  		}
   254  	}
   255  	timer.Stop()
   256  }
   257  
   258  func (store *localStore) Close() {
   259  	if store.shrinkDuration > 0 {
   260  		store.closeCh <- struct{}{}
   261  	}
   262  	close(store.closeCh)
   263  }
   264  
   265  type Entry struct {
   266  	key      string
   267  	Value    any
   268  	Deadline time.Time
   269  }
   270  
   271  func (entry *Entry) Expired() bool {
   272  	if entry.Deadline.IsZero() {
   273  		return false
   274  	}
   275  	return time.Now().After(entry.Deadline)
   276  }