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 }