github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/blobserver/proxycache/proxycache.go (about)

     1  /*
     2  Copyright 2014 The Camlistore Authors
     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 proxycache registers the "proxycache" blobserver storage type,
    19  which uses a provided blobserver as a cache for a second origin
    20  blobserver.
    21  
    22  The proxycache blobserver type also takes a sorted.KeyValue reference
    23  which it uses as the LRU for which old items to evict from the cache.
    24  
    25  Example config:
    26  
    27        "/cache/": {
    28            "handler": "storage-proxycache",
    29            "handlerArgs": {
    30  ... TODO
    31            }
    32        },
    33  */
    34  package proxycache
    35  
    36  import (
    37  	"bytes"
    38  	"fmt"
    39  	"io"
    40  	"io/ioutil"
    41  	"log"
    42  	"os"
    43  	"sync"
    44  	"time"
    45  
    46  	"camlistore.org/pkg/blob"
    47  	"camlistore.org/pkg/blobserver"
    48  	"camlistore.org/pkg/context"
    49  	"camlistore.org/pkg/jsonconfig"
    50  	"camlistore.org/pkg/sorted"
    51  )
    52  
    53  const buffered = 8
    54  
    55  type sto struct {
    56  	origin        blobserver.Storage
    57  	cache         blobserver.Storage
    58  	kv            sorted.KeyValue
    59  	maxCacheBytes int64
    60  
    61  	mu         sync.Mutex // guards cacheBytes & kv mutations
    62  	cacheBytes int64
    63  }
    64  
    65  func init() {
    66  	blobserver.RegisterStorageConstructor("proxycache", blobserver.StorageConstructor(newFromConfig))
    67  }
    68  
    69  func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) {
    70  	var (
    71  		origin        = config.RequiredString("origin")
    72  		cache         = config.RequiredString("cache")
    73  		kvConf        = config.RequiredObject("meta")
    74  		maxCacheBytes = config.OptionalInt64("maxCacheBytes", 512<<20)
    75  	)
    76  	if err := config.Validate(); err != nil {
    77  		return nil, err
    78  	}
    79  	cacheSto, err := ld.GetStorage(cache)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	originSto, err := ld.GetStorage(origin)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	kv, err := sorted.NewKeyValue(kvConf)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	// TODO: enumerate through kv and calculate current size.
    93  	// Maybe also even enumerate through cache to see if they match.
    94  	// Or even: keep it only in memory and not in kv?
    95  
    96  	s := &sto{
    97  		origin:        originSto,
    98  		cache:         cacheSto,
    99  		maxCacheBytes: maxCacheBytes,
   100  		kv:            kv,
   101  	}
   102  	return s, nil
   103  }
   104  
   105  func (sto *sto) touchBlob(sb blob.SizedRef) {
   106  	key := sb.Ref.String()
   107  	sto.mu.Lock()
   108  	defer sto.mu.Unlock()
   109  	val := fmt.Sprintf("%d:%d", sb.Size, time.Now().Unix())
   110  	_, err := sto.kv.Get(key)
   111  	new := err != nil
   112  	if err == sorted.ErrNotFound {
   113  		new = true
   114  	} else if err != nil {
   115  		log.Printf("proxycache: reading meta for key %q: %v", key, err)
   116  	}
   117  	if err := sto.kv.Set(key, val); err != nil {
   118  		log.Printf("proxycache: updating meta for %v: %v", sb, err)
   119  	}
   120  	if new {
   121  		sto.cacheBytes += int64(sb.Size)
   122  	}
   123  	if sto.cacheBytes > sto.maxCacheBytes {
   124  		// TODO: clean some stuff.
   125  	}
   126  }
   127  
   128  func (sto *sto) Fetch(b blob.Ref) (rc io.ReadCloser, size uint32, err error) {
   129  	rc, size, err = sto.cache.Fetch(b)
   130  	if err == nil {
   131  		sto.touchBlob(blob.SizedRef{b, size})
   132  		return
   133  	}
   134  	if err != os.ErrNotExist {
   135  		log.Printf("warning: proxycache cache fetch error for %v: %v", b, err)
   136  	}
   137  	rc, size, err = sto.cache.Fetch(b)
   138  	if err != nil {
   139  		return
   140  	}
   141  	all, err := ioutil.ReadAll(rc)
   142  	if err != nil {
   143  		return
   144  	}
   145  	go func() {
   146  		if _, err := blobserver.Receive(sto.cache, b, bytes.NewReader(all)); err != nil {
   147  			log.Printf("populating proxycache cache for %v: %v", b, err)
   148  			return
   149  		}
   150  		sto.touchBlob(blob.SizedRef{b, size})
   151  	}()
   152  	return ioutil.NopCloser(bytes.NewReader(all)), size, nil
   153  }
   154  
   155  func (sto *sto) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) error {
   156  	// TODO: stat from cache if possible? then at least we have
   157  	// to be sure we never have blobs in the cache that we don't have
   158  	// in the origin. For now, be paranoid and just proxy to the origin:
   159  	return sto.origin.StatBlobs(dest, blobs)
   160  }
   161  
   162  func (sto *sto) ReceiveBlob(br blob.Ref, src io.Reader) (sb blob.SizedRef, err error) {
   163  	// Slurp the whole blob before replicating. Bounded by 16 MB anyway.
   164  	var buf bytes.Buffer
   165  	if _, err = io.Copy(&buf, src); err != nil {
   166  		return
   167  	}
   168  
   169  	if _, err = sto.cache.ReceiveBlob(br, bytes.NewReader(buf.Bytes())); err != nil {
   170  		return
   171  	}
   172  	sto.touchBlob(sb)
   173  	return sto.origin.ReceiveBlob(br, bytes.NewReader(buf.Bytes()))
   174  }
   175  
   176  func (sto *sto) RemoveBlobs(blobs []blob.Ref) error {
   177  	// Ignore result of cache removal
   178  	go sto.cache.RemoveBlobs(blobs)
   179  	return sto.origin.RemoveBlobs(blobs)
   180  }
   181  
   182  func (sto *sto) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error {
   183  	return sto.origin.EnumerateBlobs(ctx, dest, after, limit)
   184  }
   185  
   186  // TODO:
   187  //var _ blobserver.Generationer = (*sto)(nil)
   188  
   189  func (sto *sto) x_ResetStorageGeneration() error {
   190  	panic("TODO")
   191  }
   192  
   193  func (sto *sto) x_StorageGeneration() (initTime time.Time, random string, err error) {
   194  	panic("TODO")
   195  }