mirror of
https://github.com/minio/minio.git
synced 2025-01-15 08:45:00 -05:00
05f8654e3d
This largely avoids a large buffer copy which would accumulate inside proxyReader{} This patch also implements "initialize()" function to init and populate data on all the existing buckets, avoiding the redundant ListBuckets() invoked by every API call.
216 lines
4.9 KiB
Go
216 lines
4.9 KiB
Go
/*
|
|
* Minimalist Object Storage, (C) 2015 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Package trove implements in memory caching methods
|
|
package trove
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var noExpiration = time.Duration(0)
|
|
|
|
// Cache holds the required variables to compose an in memory cache system
|
|
// which also provides expiring key mechanism and also maxSize
|
|
type Cache struct {
|
|
// Mutex is used for handling the concurrent
|
|
// read/write requests for cache
|
|
sync.Mutex
|
|
|
|
// items hold the cached objects
|
|
items map[string][]byte
|
|
|
|
// updatedAt holds the time that related item's updated at
|
|
updatedAt map[string]time.Time
|
|
|
|
// expiration is a duration for a cache key to expire
|
|
expiration time.Duration
|
|
|
|
// stopExpireTimer channel to quit the timer thread
|
|
stopExpireTimer chan struct{}
|
|
|
|
// maxSize is a total size for overall cache
|
|
maxSize uint64
|
|
|
|
// currentSize is a current size in memory
|
|
currentSize uint64
|
|
|
|
// OnExpired - callback function for eviction
|
|
OnExpired func(a ...interface{})
|
|
|
|
// totalExpired counter to keep track of total expirations
|
|
totalExpired uint64
|
|
}
|
|
|
|
// Stats current cache statistics
|
|
type Stats struct {
|
|
Bytes uint64
|
|
Items uint64
|
|
Expired uint64
|
|
}
|
|
|
|
// NewCache creates an inmemory cache
|
|
//
|
|
// maxSize is used for expiring objects before we run out of memory
|
|
// expiration is used for expiration of a key from cache
|
|
func NewCache(maxSize uint64, expiration time.Duration) *Cache {
|
|
return &Cache{
|
|
items: make(map[string][]byte),
|
|
updatedAt: map[string]time.Time{},
|
|
expiration: expiration,
|
|
maxSize: maxSize,
|
|
}
|
|
}
|
|
|
|
// Stats get current cache statistics
|
|
func (r *Cache) Stats() Stats {
|
|
return Stats{
|
|
Bytes: r.currentSize,
|
|
Items: uint64(len(r.items)),
|
|
Expired: r.totalExpired,
|
|
}
|
|
}
|
|
|
|
// ExpireObjects expire objects in go routine
|
|
func (r *Cache) ExpireObjects(gcInterval time.Duration) {
|
|
r.stopExpireTimer = make(chan struct{})
|
|
ticker := time.NewTicker(gcInterval)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
r.Expire()
|
|
case <-r.stopExpireTimer:
|
|
ticker.Stop()
|
|
return
|
|
}
|
|
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Get returns a value of a given key if it exists
|
|
func (r *Cache) Get(key string) ([]byte, bool) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
value, ok := r.items[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
r.updatedAt[key] = time.Now()
|
|
return value, true
|
|
}
|
|
|
|
// Append will append new data to an existing key,
|
|
// if key doesn't exist it behaves like Set()
|
|
func (r *Cache) Append(key string, value []byte) bool {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
valueLen := uint64(len(value))
|
|
if r.maxSize > 0 {
|
|
// check if the size of the object is not bigger than the
|
|
// capacity of the cache
|
|
if valueLen > r.maxSize {
|
|
return false
|
|
}
|
|
// remove random key if only we reach the maxSize threshold
|
|
for (r.currentSize + valueLen) > r.maxSize {
|
|
for randomKey := range r.items {
|
|
r.doDelete(randomKey)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
_, ok := r.items[key]
|
|
if !ok {
|
|
r.items[key] = value
|
|
r.currentSize += valueLen
|
|
r.updatedAt[key] = time.Now()
|
|
return true
|
|
}
|
|
r.items[key] = append(r.items[key], value...)
|
|
r.currentSize += valueLen
|
|
r.updatedAt[key] = time.Now()
|
|
return true
|
|
}
|
|
|
|
// Set will persist a value to the cache
|
|
func (r *Cache) Set(key string, value []byte) bool {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
valueLen := uint64(len(value))
|
|
if r.maxSize > 0 {
|
|
// check if the size of the object is not bigger than the
|
|
// capacity of the cache
|
|
if valueLen > r.maxSize {
|
|
return false
|
|
}
|
|
// remove random key if only we reach the maxSize threshold
|
|
for (r.currentSize + valueLen) > r.maxSize {
|
|
for randomKey := range r.items {
|
|
r.doDelete(randomKey)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
r.items[key] = value
|
|
r.currentSize += valueLen
|
|
r.updatedAt[key] = time.Now()
|
|
return true
|
|
}
|
|
|
|
// Expire expires keys which have expired
|
|
func (r *Cache) Expire() {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
for key := range r.items {
|
|
if !r.isValid(key) {
|
|
r.doDelete(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete deletes a given key if exists
|
|
func (r *Cache) Delete(key string) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
r.doDelete(key)
|
|
}
|
|
|
|
func (r *Cache) doDelete(key string) {
|
|
if _, ok := r.items[key]; ok {
|
|
r.currentSize -= uint64(len(r.items[key]))
|
|
delete(r.items, key)
|
|
delete(r.updatedAt, key)
|
|
r.totalExpired++
|
|
if r.OnExpired != nil {
|
|
r.OnExpired(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Cache) isValid(key string) bool {
|
|
updatedAt, ok := r.updatedAt[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if r.expiration == noExpiration {
|
|
return true
|
|
}
|
|
return updatedAt.Add(r.expiration).After(time.Now())
|
|
}
|