├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cache ├── cache.pb.go └── cache.proto ├── lru ├── lru.go └── lru_test.go ├── main.go └── server ├── server.go └── server_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.x 5 | - 1.8.x 6 | - tip 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Josh Rotenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grpc-cache 2 | 3 | [![Build Status](https://travis-ci.org/joshrotenberg/grpc-cache.svg?branch=master)](https://travis-ci.org/joshrotenberg/grpc-cache) [![GoDoc](https://godoc.org/github.com/joshrotenberg/grpc-cache?status.svg)](https://godoc.org/github.com/joshrotenberg/grpc-cache) 4 | 5 | `grpc-cache` is a ~[memcached](https://memcached.org/) clone written in [Go](https://golang.org) using [gRPC](http://www.grpc.io/) for the protocol layer. It is currently being developed and for now is just for fun. 6 | 7 | -------------------------------------------------------------------------------- /cache/cache.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: cache.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package cache is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | cache.proto 10 | 11 | It has these top-level messages: 12 | CacheItem 13 | CacheRequest 14 | CacheResponse 15 | */ 16 | package cache 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | 22 | import ( 23 | context "golang.org/x/net/context" 24 | grpc "google.golang.org/grpc" 25 | ) 26 | 27 | // Reference imports to suppress errors if they are not otherwise used. 28 | var _ = proto.Marshal 29 | var _ = fmt.Errorf 30 | var _ = math.Inf 31 | 32 | // This is a compile-time assertion to ensure that this generated file 33 | // is compatible with the proto package it is being compiled against. 34 | // A compilation error at this line likely means your copy of the 35 | // proto package needs to be updated. 36 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 37 | 38 | type CacheRequest_Operation int32 39 | 40 | const ( 41 | CacheRequest_NOOP CacheRequest_Operation = 0 42 | CacheRequest_SET CacheRequest_Operation = 1 43 | CacheRequest_CAS CacheRequest_Operation = 2 44 | CacheRequest_GET CacheRequest_Operation = 3 45 | CacheRequest_GETS CacheRequest_Operation = 4 46 | CacheRequest_ADD CacheRequest_Operation = 5 47 | CacheRequest_REPLACE CacheRequest_Operation = 6 48 | CacheRequest_DELETE CacheRequest_Operation = 7 49 | CacheRequest_TOUCH CacheRequest_Operation = 8 50 | CacheRequest_APPEND CacheRequest_Operation = 9 51 | CacheRequest_PREPEND CacheRequest_Operation = 10 52 | CacheRequest_INCREMENT CacheRequest_Operation = 11 53 | CacheRequest_DECREMENT CacheRequest_Operation = 12 54 | CacheRequest_FLUSHALL CacheRequest_Operation = 13 55 | ) 56 | 57 | var CacheRequest_Operation_name = map[int32]string{ 58 | 0: "NOOP", 59 | 1: "SET", 60 | 2: "CAS", 61 | 3: "GET", 62 | 4: "GETS", 63 | 5: "ADD", 64 | 6: "REPLACE", 65 | 7: "DELETE", 66 | 8: "TOUCH", 67 | 9: "APPEND", 68 | 10: "PREPEND", 69 | 11: "INCREMENT", 70 | 12: "DECREMENT", 71 | 13: "FLUSHALL", 72 | } 73 | var CacheRequest_Operation_value = map[string]int32{ 74 | "NOOP": 0, 75 | "SET": 1, 76 | "CAS": 2, 77 | "GET": 3, 78 | "GETS": 4, 79 | "ADD": 5, 80 | "REPLACE": 6, 81 | "DELETE": 7, 82 | "TOUCH": 8, 83 | "APPEND": 9, 84 | "PREPEND": 10, 85 | "INCREMENT": 11, 86 | "DECREMENT": 12, 87 | "FLUSHALL": 13, 88 | } 89 | 90 | func (x CacheRequest_Operation) String() string { 91 | return proto.EnumName(CacheRequest_Operation_name, int32(x)) 92 | } 93 | func (CacheRequest_Operation) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} } 94 | 95 | // CacheItem encapsulates any in/out cache values into a single message 96 | // structure. Some values may not be pertinent in some situations (i.e. you 97 | // can't set the cas). 98 | type CacheItem struct { 99 | Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` 100 | Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 101 | Ttl uint64 `protobuf:"varint,3,opt,name=ttl" json:"ttl,omitempty"` 102 | Cas uint64 `protobuf:"varint,4,opt,name=cas" json:"cas,omitempty"` 103 | } 104 | 105 | func (m *CacheItem) Reset() { *m = CacheItem{} } 106 | func (m *CacheItem) String() string { return proto.CompactTextString(m) } 107 | func (*CacheItem) ProtoMessage() {} 108 | func (*CacheItem) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 109 | 110 | func (m *CacheItem) GetKey() string { 111 | if m != nil { 112 | return m.Key 113 | } 114 | return "" 115 | } 116 | 117 | func (m *CacheItem) GetValue() []byte { 118 | if m != nil { 119 | return m.Value 120 | } 121 | return nil 122 | } 123 | 124 | func (m *CacheItem) GetTtl() uint64 { 125 | if m != nil { 126 | return m.Ttl 127 | } 128 | return 0 129 | } 130 | 131 | func (m *CacheItem) GetCas() uint64 { 132 | if m != nil { 133 | return m.Cas 134 | } 135 | return 0 136 | } 137 | 138 | type CacheRequest struct { 139 | Operation CacheRequest_Operation `protobuf:"varint,1,opt,name=operation,enum=cache.CacheRequest_Operation" json:"operation,omitempty"` 140 | Item *CacheItem `protobuf:"bytes,2,opt,name=item" json:"item,omitempty"` 141 | Append []byte `protobuf:"bytes,3,opt,name=append,proto3" json:"append,omitempty"` 142 | Prepend []byte `protobuf:"bytes,4,opt,name=prepend,proto3" json:"prepend,omitempty"` 143 | Increment uint64 `protobuf:"varint,5,opt,name=increment" json:"increment,omitempty"` 144 | Decrement uint64 `protobuf:"varint,6,opt,name=decrement" json:"decrement,omitempty"` 145 | } 146 | 147 | func (m *CacheRequest) Reset() { *m = CacheRequest{} } 148 | func (m *CacheRequest) String() string { return proto.CompactTextString(m) } 149 | func (*CacheRequest) ProtoMessage() {} 150 | func (*CacheRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 151 | 152 | func (m *CacheRequest) GetOperation() CacheRequest_Operation { 153 | if m != nil { 154 | return m.Operation 155 | } 156 | return CacheRequest_NOOP 157 | } 158 | 159 | func (m *CacheRequest) GetItem() *CacheItem { 160 | if m != nil { 161 | return m.Item 162 | } 163 | return nil 164 | } 165 | 166 | func (m *CacheRequest) GetAppend() []byte { 167 | if m != nil { 168 | return m.Append 169 | } 170 | return nil 171 | } 172 | 173 | func (m *CacheRequest) GetPrepend() []byte { 174 | if m != nil { 175 | return m.Prepend 176 | } 177 | return nil 178 | } 179 | 180 | func (m *CacheRequest) GetIncrement() uint64 { 181 | if m != nil { 182 | return m.Increment 183 | } 184 | return 0 185 | } 186 | 187 | func (m *CacheRequest) GetDecrement() uint64 { 188 | if m != nil { 189 | return m.Decrement 190 | } 191 | return 0 192 | } 193 | 194 | type CacheResponse struct { 195 | Item *CacheItem `protobuf:"bytes,1,opt,name=item" json:"item,omitempty"` 196 | } 197 | 198 | func (m *CacheResponse) Reset() { *m = CacheResponse{} } 199 | func (m *CacheResponse) String() string { return proto.CompactTextString(m) } 200 | func (*CacheResponse) ProtoMessage() {} 201 | func (*CacheResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 202 | 203 | func (m *CacheResponse) GetItem() *CacheItem { 204 | if m != nil { 205 | return m.Item 206 | } 207 | return nil 208 | } 209 | 210 | func init() { 211 | proto.RegisterType((*CacheItem)(nil), "cache.CacheItem") 212 | proto.RegisterType((*CacheRequest)(nil), "cache.CacheRequest") 213 | proto.RegisterType((*CacheResponse)(nil), "cache.CacheResponse") 214 | proto.RegisterEnum("cache.CacheRequest_Operation", CacheRequest_Operation_name, CacheRequest_Operation_value) 215 | } 216 | 217 | // Reference imports to suppress errors if they are not otherwise used. 218 | var _ context.Context 219 | var _ grpc.ClientConn 220 | 221 | // This is a compile-time assertion to ensure that this generated file 222 | // is compatible with the grpc package it is being compiled against. 223 | const _ = grpc.SupportPackageIsVersion4 224 | 225 | // Client API for Cache service 226 | 227 | type CacheClient interface { 228 | // streams cache request/response 229 | Stream(ctx context.Context, opts ...grpc.CallOption) (Cache_StreamClient, error) 230 | // single cache request/response 231 | Call(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 232 | // convenience calls for all cache operations. these 233 | // just add the Operation and forward on to Call 234 | Set(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 235 | Cas(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 236 | Get(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 237 | Gets(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 238 | Add(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 239 | Replace(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 240 | Delete(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 241 | Touch(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 242 | Append(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 243 | Prepend(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 244 | Increment(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 245 | Decrement(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 246 | FlushAll(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) 247 | } 248 | 249 | type cacheClient struct { 250 | cc *grpc.ClientConn 251 | } 252 | 253 | func NewCacheClient(cc *grpc.ClientConn) CacheClient { 254 | return &cacheClient{cc} 255 | } 256 | 257 | func (c *cacheClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Cache_StreamClient, error) { 258 | stream, err := grpc.NewClientStream(ctx, &_Cache_serviceDesc.Streams[0], c.cc, "/cache.Cache/Stream", opts...) 259 | if err != nil { 260 | return nil, err 261 | } 262 | x := &cacheStreamClient{stream} 263 | return x, nil 264 | } 265 | 266 | type Cache_StreamClient interface { 267 | Send(*CacheRequest) error 268 | Recv() (*CacheResponse, error) 269 | grpc.ClientStream 270 | } 271 | 272 | type cacheStreamClient struct { 273 | grpc.ClientStream 274 | } 275 | 276 | func (x *cacheStreamClient) Send(m *CacheRequest) error { 277 | return x.ClientStream.SendMsg(m) 278 | } 279 | 280 | func (x *cacheStreamClient) Recv() (*CacheResponse, error) { 281 | m := new(CacheResponse) 282 | if err := x.ClientStream.RecvMsg(m); err != nil { 283 | return nil, err 284 | } 285 | return m, nil 286 | } 287 | 288 | func (c *cacheClient) Call(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 289 | out := new(CacheResponse) 290 | err := grpc.Invoke(ctx, "/cache.Cache/Call", in, out, c.cc, opts...) 291 | if err != nil { 292 | return nil, err 293 | } 294 | return out, nil 295 | } 296 | 297 | func (c *cacheClient) Set(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 298 | out := new(CacheResponse) 299 | err := grpc.Invoke(ctx, "/cache.Cache/Set", in, out, c.cc, opts...) 300 | if err != nil { 301 | return nil, err 302 | } 303 | return out, nil 304 | } 305 | 306 | func (c *cacheClient) Cas(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 307 | out := new(CacheResponse) 308 | err := grpc.Invoke(ctx, "/cache.Cache/Cas", in, out, c.cc, opts...) 309 | if err != nil { 310 | return nil, err 311 | } 312 | return out, nil 313 | } 314 | 315 | func (c *cacheClient) Get(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 316 | out := new(CacheResponse) 317 | err := grpc.Invoke(ctx, "/cache.Cache/Get", in, out, c.cc, opts...) 318 | if err != nil { 319 | return nil, err 320 | } 321 | return out, nil 322 | } 323 | 324 | func (c *cacheClient) Gets(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 325 | out := new(CacheResponse) 326 | err := grpc.Invoke(ctx, "/cache.Cache/Gets", in, out, c.cc, opts...) 327 | if err != nil { 328 | return nil, err 329 | } 330 | return out, nil 331 | } 332 | 333 | func (c *cacheClient) Add(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 334 | out := new(CacheResponse) 335 | err := grpc.Invoke(ctx, "/cache.Cache/Add", in, out, c.cc, opts...) 336 | if err != nil { 337 | return nil, err 338 | } 339 | return out, nil 340 | } 341 | 342 | func (c *cacheClient) Replace(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 343 | out := new(CacheResponse) 344 | err := grpc.Invoke(ctx, "/cache.Cache/Replace", in, out, c.cc, opts...) 345 | if err != nil { 346 | return nil, err 347 | } 348 | return out, nil 349 | } 350 | 351 | func (c *cacheClient) Delete(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 352 | out := new(CacheResponse) 353 | err := grpc.Invoke(ctx, "/cache.Cache/Delete", in, out, c.cc, opts...) 354 | if err != nil { 355 | return nil, err 356 | } 357 | return out, nil 358 | } 359 | 360 | func (c *cacheClient) Touch(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 361 | out := new(CacheResponse) 362 | err := grpc.Invoke(ctx, "/cache.Cache/Touch", in, out, c.cc, opts...) 363 | if err != nil { 364 | return nil, err 365 | } 366 | return out, nil 367 | } 368 | 369 | func (c *cacheClient) Append(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 370 | out := new(CacheResponse) 371 | err := grpc.Invoke(ctx, "/cache.Cache/Append", in, out, c.cc, opts...) 372 | if err != nil { 373 | return nil, err 374 | } 375 | return out, nil 376 | } 377 | 378 | func (c *cacheClient) Prepend(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 379 | out := new(CacheResponse) 380 | err := grpc.Invoke(ctx, "/cache.Cache/Prepend", in, out, c.cc, opts...) 381 | if err != nil { 382 | return nil, err 383 | } 384 | return out, nil 385 | } 386 | 387 | func (c *cacheClient) Increment(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 388 | out := new(CacheResponse) 389 | err := grpc.Invoke(ctx, "/cache.Cache/Increment", in, out, c.cc, opts...) 390 | if err != nil { 391 | return nil, err 392 | } 393 | return out, nil 394 | } 395 | 396 | func (c *cacheClient) Decrement(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 397 | out := new(CacheResponse) 398 | err := grpc.Invoke(ctx, "/cache.Cache/Decrement", in, out, c.cc, opts...) 399 | if err != nil { 400 | return nil, err 401 | } 402 | return out, nil 403 | } 404 | 405 | func (c *cacheClient) FlushAll(ctx context.Context, in *CacheRequest, opts ...grpc.CallOption) (*CacheResponse, error) { 406 | out := new(CacheResponse) 407 | err := grpc.Invoke(ctx, "/cache.Cache/FlushAll", in, out, c.cc, opts...) 408 | if err != nil { 409 | return nil, err 410 | } 411 | return out, nil 412 | } 413 | 414 | // Server API for Cache service 415 | 416 | type CacheServer interface { 417 | // streams cache request/response 418 | Stream(Cache_StreamServer) error 419 | // single cache request/response 420 | Call(context.Context, *CacheRequest) (*CacheResponse, error) 421 | // convenience calls for all cache operations. these 422 | // just add the Operation and forward on to Call 423 | Set(context.Context, *CacheRequest) (*CacheResponse, error) 424 | Cas(context.Context, *CacheRequest) (*CacheResponse, error) 425 | Get(context.Context, *CacheRequest) (*CacheResponse, error) 426 | Gets(context.Context, *CacheRequest) (*CacheResponse, error) 427 | Add(context.Context, *CacheRequest) (*CacheResponse, error) 428 | Replace(context.Context, *CacheRequest) (*CacheResponse, error) 429 | Delete(context.Context, *CacheRequest) (*CacheResponse, error) 430 | Touch(context.Context, *CacheRequest) (*CacheResponse, error) 431 | Append(context.Context, *CacheRequest) (*CacheResponse, error) 432 | Prepend(context.Context, *CacheRequest) (*CacheResponse, error) 433 | Increment(context.Context, *CacheRequest) (*CacheResponse, error) 434 | Decrement(context.Context, *CacheRequest) (*CacheResponse, error) 435 | FlushAll(context.Context, *CacheRequest) (*CacheResponse, error) 436 | } 437 | 438 | func RegisterCacheServer(s *grpc.Server, srv CacheServer) { 439 | s.RegisterService(&_Cache_serviceDesc, srv) 440 | } 441 | 442 | func _Cache_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { 443 | return srv.(CacheServer).Stream(&cacheStreamServer{stream}) 444 | } 445 | 446 | type Cache_StreamServer interface { 447 | Send(*CacheResponse) error 448 | Recv() (*CacheRequest, error) 449 | grpc.ServerStream 450 | } 451 | 452 | type cacheStreamServer struct { 453 | grpc.ServerStream 454 | } 455 | 456 | func (x *cacheStreamServer) Send(m *CacheResponse) error { 457 | return x.ServerStream.SendMsg(m) 458 | } 459 | 460 | func (x *cacheStreamServer) Recv() (*CacheRequest, error) { 461 | m := new(CacheRequest) 462 | if err := x.ServerStream.RecvMsg(m); err != nil { 463 | return nil, err 464 | } 465 | return m, nil 466 | } 467 | 468 | func _Cache_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 469 | in := new(CacheRequest) 470 | if err := dec(in); err != nil { 471 | return nil, err 472 | } 473 | if interceptor == nil { 474 | return srv.(CacheServer).Call(ctx, in) 475 | } 476 | info := &grpc.UnaryServerInfo{ 477 | Server: srv, 478 | FullMethod: "/cache.Cache/Call", 479 | } 480 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 481 | return srv.(CacheServer).Call(ctx, req.(*CacheRequest)) 482 | } 483 | return interceptor(ctx, in, info, handler) 484 | } 485 | 486 | func _Cache_Set_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 487 | in := new(CacheRequest) 488 | if err := dec(in); err != nil { 489 | return nil, err 490 | } 491 | if interceptor == nil { 492 | return srv.(CacheServer).Set(ctx, in) 493 | } 494 | info := &grpc.UnaryServerInfo{ 495 | Server: srv, 496 | FullMethod: "/cache.Cache/Set", 497 | } 498 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 499 | return srv.(CacheServer).Set(ctx, req.(*CacheRequest)) 500 | } 501 | return interceptor(ctx, in, info, handler) 502 | } 503 | 504 | func _Cache_Cas_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 505 | in := new(CacheRequest) 506 | if err := dec(in); err != nil { 507 | return nil, err 508 | } 509 | if interceptor == nil { 510 | return srv.(CacheServer).Cas(ctx, in) 511 | } 512 | info := &grpc.UnaryServerInfo{ 513 | Server: srv, 514 | FullMethod: "/cache.Cache/Cas", 515 | } 516 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 517 | return srv.(CacheServer).Cas(ctx, req.(*CacheRequest)) 518 | } 519 | return interceptor(ctx, in, info, handler) 520 | } 521 | 522 | func _Cache_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 523 | in := new(CacheRequest) 524 | if err := dec(in); err != nil { 525 | return nil, err 526 | } 527 | if interceptor == nil { 528 | return srv.(CacheServer).Get(ctx, in) 529 | } 530 | info := &grpc.UnaryServerInfo{ 531 | Server: srv, 532 | FullMethod: "/cache.Cache/Get", 533 | } 534 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 535 | return srv.(CacheServer).Get(ctx, req.(*CacheRequest)) 536 | } 537 | return interceptor(ctx, in, info, handler) 538 | } 539 | 540 | func _Cache_Gets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 541 | in := new(CacheRequest) 542 | if err := dec(in); err != nil { 543 | return nil, err 544 | } 545 | if interceptor == nil { 546 | return srv.(CacheServer).Gets(ctx, in) 547 | } 548 | info := &grpc.UnaryServerInfo{ 549 | Server: srv, 550 | FullMethod: "/cache.Cache/Gets", 551 | } 552 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 553 | return srv.(CacheServer).Gets(ctx, req.(*CacheRequest)) 554 | } 555 | return interceptor(ctx, in, info, handler) 556 | } 557 | 558 | func _Cache_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 559 | in := new(CacheRequest) 560 | if err := dec(in); err != nil { 561 | return nil, err 562 | } 563 | if interceptor == nil { 564 | return srv.(CacheServer).Add(ctx, in) 565 | } 566 | info := &grpc.UnaryServerInfo{ 567 | Server: srv, 568 | FullMethod: "/cache.Cache/Add", 569 | } 570 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 571 | return srv.(CacheServer).Add(ctx, req.(*CacheRequest)) 572 | } 573 | return interceptor(ctx, in, info, handler) 574 | } 575 | 576 | func _Cache_Replace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 577 | in := new(CacheRequest) 578 | if err := dec(in); err != nil { 579 | return nil, err 580 | } 581 | if interceptor == nil { 582 | return srv.(CacheServer).Replace(ctx, in) 583 | } 584 | info := &grpc.UnaryServerInfo{ 585 | Server: srv, 586 | FullMethod: "/cache.Cache/Replace", 587 | } 588 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 589 | return srv.(CacheServer).Replace(ctx, req.(*CacheRequest)) 590 | } 591 | return interceptor(ctx, in, info, handler) 592 | } 593 | 594 | func _Cache_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 595 | in := new(CacheRequest) 596 | if err := dec(in); err != nil { 597 | return nil, err 598 | } 599 | if interceptor == nil { 600 | return srv.(CacheServer).Delete(ctx, in) 601 | } 602 | info := &grpc.UnaryServerInfo{ 603 | Server: srv, 604 | FullMethod: "/cache.Cache/Delete", 605 | } 606 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 607 | return srv.(CacheServer).Delete(ctx, req.(*CacheRequest)) 608 | } 609 | return interceptor(ctx, in, info, handler) 610 | } 611 | 612 | func _Cache_Touch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 613 | in := new(CacheRequest) 614 | if err := dec(in); err != nil { 615 | return nil, err 616 | } 617 | if interceptor == nil { 618 | return srv.(CacheServer).Touch(ctx, in) 619 | } 620 | info := &grpc.UnaryServerInfo{ 621 | Server: srv, 622 | FullMethod: "/cache.Cache/Touch", 623 | } 624 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 625 | return srv.(CacheServer).Touch(ctx, req.(*CacheRequest)) 626 | } 627 | return interceptor(ctx, in, info, handler) 628 | } 629 | 630 | func _Cache_Append_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 631 | in := new(CacheRequest) 632 | if err := dec(in); err != nil { 633 | return nil, err 634 | } 635 | if interceptor == nil { 636 | return srv.(CacheServer).Append(ctx, in) 637 | } 638 | info := &grpc.UnaryServerInfo{ 639 | Server: srv, 640 | FullMethod: "/cache.Cache/Append", 641 | } 642 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 643 | return srv.(CacheServer).Append(ctx, req.(*CacheRequest)) 644 | } 645 | return interceptor(ctx, in, info, handler) 646 | } 647 | 648 | func _Cache_Prepend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 649 | in := new(CacheRequest) 650 | if err := dec(in); err != nil { 651 | return nil, err 652 | } 653 | if interceptor == nil { 654 | return srv.(CacheServer).Prepend(ctx, in) 655 | } 656 | info := &grpc.UnaryServerInfo{ 657 | Server: srv, 658 | FullMethod: "/cache.Cache/Prepend", 659 | } 660 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 661 | return srv.(CacheServer).Prepend(ctx, req.(*CacheRequest)) 662 | } 663 | return interceptor(ctx, in, info, handler) 664 | } 665 | 666 | func _Cache_Increment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 667 | in := new(CacheRequest) 668 | if err := dec(in); err != nil { 669 | return nil, err 670 | } 671 | if interceptor == nil { 672 | return srv.(CacheServer).Increment(ctx, in) 673 | } 674 | info := &grpc.UnaryServerInfo{ 675 | Server: srv, 676 | FullMethod: "/cache.Cache/Increment", 677 | } 678 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 679 | return srv.(CacheServer).Increment(ctx, req.(*CacheRequest)) 680 | } 681 | return interceptor(ctx, in, info, handler) 682 | } 683 | 684 | func _Cache_Decrement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 685 | in := new(CacheRequest) 686 | if err := dec(in); err != nil { 687 | return nil, err 688 | } 689 | if interceptor == nil { 690 | return srv.(CacheServer).Decrement(ctx, in) 691 | } 692 | info := &grpc.UnaryServerInfo{ 693 | Server: srv, 694 | FullMethod: "/cache.Cache/Decrement", 695 | } 696 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 697 | return srv.(CacheServer).Decrement(ctx, req.(*CacheRequest)) 698 | } 699 | return interceptor(ctx, in, info, handler) 700 | } 701 | 702 | func _Cache_FlushAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 703 | in := new(CacheRequest) 704 | if err := dec(in); err != nil { 705 | return nil, err 706 | } 707 | if interceptor == nil { 708 | return srv.(CacheServer).FlushAll(ctx, in) 709 | } 710 | info := &grpc.UnaryServerInfo{ 711 | Server: srv, 712 | FullMethod: "/cache.Cache/FlushAll", 713 | } 714 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 715 | return srv.(CacheServer).FlushAll(ctx, req.(*CacheRequest)) 716 | } 717 | return interceptor(ctx, in, info, handler) 718 | } 719 | 720 | var _Cache_serviceDesc = grpc.ServiceDesc{ 721 | ServiceName: "cache.Cache", 722 | HandlerType: (*CacheServer)(nil), 723 | Methods: []grpc.MethodDesc{ 724 | { 725 | MethodName: "Call", 726 | Handler: _Cache_Call_Handler, 727 | }, 728 | { 729 | MethodName: "Set", 730 | Handler: _Cache_Set_Handler, 731 | }, 732 | { 733 | MethodName: "Cas", 734 | Handler: _Cache_Cas_Handler, 735 | }, 736 | { 737 | MethodName: "Get", 738 | Handler: _Cache_Get_Handler, 739 | }, 740 | { 741 | MethodName: "Gets", 742 | Handler: _Cache_Gets_Handler, 743 | }, 744 | { 745 | MethodName: "Add", 746 | Handler: _Cache_Add_Handler, 747 | }, 748 | { 749 | MethodName: "Replace", 750 | Handler: _Cache_Replace_Handler, 751 | }, 752 | { 753 | MethodName: "Delete", 754 | Handler: _Cache_Delete_Handler, 755 | }, 756 | { 757 | MethodName: "Touch", 758 | Handler: _Cache_Touch_Handler, 759 | }, 760 | { 761 | MethodName: "Append", 762 | Handler: _Cache_Append_Handler, 763 | }, 764 | { 765 | MethodName: "Prepend", 766 | Handler: _Cache_Prepend_Handler, 767 | }, 768 | { 769 | MethodName: "Increment", 770 | Handler: _Cache_Increment_Handler, 771 | }, 772 | { 773 | MethodName: "Decrement", 774 | Handler: _Cache_Decrement_Handler, 775 | }, 776 | { 777 | MethodName: "FlushAll", 778 | Handler: _Cache_FlushAll_Handler, 779 | }, 780 | }, 781 | Streams: []grpc.StreamDesc{ 782 | { 783 | StreamName: "Stream", 784 | Handler: _Cache_Stream_Handler, 785 | ServerStreams: true, 786 | ClientStreams: true, 787 | }, 788 | }, 789 | Metadata: "cache.proto", 790 | } 791 | 792 | func init() { proto.RegisterFile("cache.proto", fileDescriptor0) } 793 | 794 | var fileDescriptor0 = []byte{ 795 | // 499 bytes of a gzipped FileDescriptorProto 796 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x8a, 0xda, 0x40, 797 | 0x14, 0xde, 0x6c, 0x7e, 0x34, 0x47, 0x2d, 0xc3, 0x74, 0x29, 0xa1, 0xb4, 0x20, 0xd2, 0x0b, 0xaf, 798 | 0xa4, 0x68, 0xb7, 0x3f, 0xf4, 0x2a, 0x24, 0xb3, 0xae, 0x60, 0x35, 0x4c, 0xb2, 0xf4, 0x7a, 0x1a, 799 | 0x0f, 0x28, 0x8d, 0x26, 0x35, 0x63, 0xa1, 0x2f, 0xd1, 0x27, 0xe9, 0x2b, 0xf4, 0xdd, 0xca, 0x4c, 800 | 0xd4, 0xdd, 0x42, 0x29, 0xcc, 0xde, 0x9d, 0xef, 0xcf, 0xf3, 0x39, 0xc9, 0x04, 0x3a, 0xb9, 0xc8, 801 | 0xd7, 0x38, 0xaa, 0xf6, 0xa5, 0x2c, 0xa9, 0xab, 0xc1, 0xe0, 0x33, 0xf8, 0x91, 0x1a, 0x66, 0x12, 802 | 0xb7, 0x94, 0x80, 0xfd, 0x15, 0x7f, 0x04, 0x56, 0xdf, 0x1a, 0xfa, 0x5c, 0x8d, 0xf4, 0x0a, 0xdc, 803 | 0xef, 0xa2, 0x38, 0x60, 0x70, 0xd9, 0xb7, 0x86, 0x5d, 0xde, 0x00, 0xe5, 0x93, 0xb2, 0x08, 0xec, 804 | 0xbe, 0x35, 0x74, 0xb8, 0x1a, 0x15, 0x93, 0x8b, 0x3a, 0x70, 0x1a, 0x26, 0x17, 0xf5, 0xe0, 0xa7, 805 | 0x0d, 0x5d, 0xfd, 0xcb, 0x1c, 0xbf, 0x1d, 0xb0, 0x96, 0xf4, 0x23, 0xf8, 0x65, 0x85, 0x7b, 0x21, 806 | 0x37, 0xe5, 0x4e, 0xaf, 0x78, 0x32, 0x7e, 0x39, 0x6a, 0x1a, 0x3d, 0xf4, 0x8d, 0x96, 0x27, 0x13, 807 | 0xbf, 0xf7, 0xd3, 0x57, 0xe0, 0x6c, 0x24, 0x6e, 0x75, 0x8d, 0xce, 0x98, 0x3c, 0xcc, 0xa9, 0xe6, 808 | 0x5c, 0xab, 0xf4, 0x19, 0x78, 0xa2, 0xaa, 0x70, 0xb7, 0xd2, 0xd5, 0xba, 0xfc, 0x88, 0x68, 0x00, 809 | 0xad, 0x6a, 0x8f, 0x5a, 0x70, 0xb4, 0x70, 0x82, 0xf4, 0x05, 0xf8, 0x9b, 0x5d, 0xbe, 0xc7, 0x2d, 810 | 0xee, 0x64, 0xe0, 0xea, 0xf6, 0xf7, 0x84, 0x52, 0x57, 0x78, 0x52, 0xbd, 0x46, 0x3d, 0x13, 0x83, 811 | 0x5f, 0x16, 0xf8, 0xe7, 0xb2, 0xb4, 0x0d, 0xce, 0x62, 0xb9, 0x4c, 0xc8, 0x05, 0x6d, 0x81, 0x9d, 812 | 0xb2, 0x8c, 0x58, 0x6a, 0x88, 0xc2, 0x94, 0x5c, 0xaa, 0x61, 0xca, 0x32, 0x62, 0x2b, 0xd3, 0x94, 813 | 0x65, 0x29, 0x71, 0x14, 0x15, 0xc6, 0x31, 0x71, 0x69, 0x07, 0x5a, 0x9c, 0x25, 0xf3, 0x30, 0x62, 814 | 0xc4, 0xa3, 0x00, 0x5e, 0xcc, 0xe6, 0x2c, 0x63, 0xa4, 0x45, 0x7d, 0x70, 0xb3, 0xe5, 0x5d, 0x74, 815 | 0x4b, 0xda, 0x8a, 0x0e, 0x93, 0x84, 0x2d, 0x62, 0xe2, 0x2b, 0x7f, 0xc2, 0x99, 0x06, 0x40, 0x7b, 816 | 0xe0, 0xcf, 0x16, 0x11, 0x67, 0x9f, 0xd8, 0x22, 0x23, 0x1d, 0x05, 0x63, 0x76, 0x82, 0x5d, 0xda, 817 | 0x85, 0xf6, 0xcd, 0xfc, 0x2e, 0xbd, 0x0d, 0xe7, 0x73, 0xd2, 0x1b, 0x5c, 0x43, 0xef, 0x78, 0xce, 818 | 0x75, 0x55, 0xee, 0x6a, 0x3c, 0x9f, 0xa9, 0xf5, 0xbf, 0x33, 0x1d, 0xff, 0xf6, 0xc0, 0xd5, 0x1c, 819 | 0xfd, 0x00, 0x5e, 0x2a, 0xf7, 0x28, 0xb6, 0xf4, 0xe9, 0x3f, 0x9e, 0xdb, 0xf3, 0xab, 0xbf, 0xc9, 820 | 0x66, 0xc9, 0xe0, 0x62, 0x68, 0xbd, 0xb6, 0xe8, 0x04, 0x9c, 0x48, 0x14, 0x85, 0x51, 0x90, 0x8e, 821 | 0xc1, 0x4e, 0x51, 0x1a, 0x67, 0x22, 0x51, 0x1b, 0x67, 0xa6, 0xa6, 0x7b, 0x26, 0xe0, 0x4c, 0x51, 822 | 0x9a, 0x2f, 0x0a, 0x57, 0x2b, 0xb3, 0xcc, 0x5b, 0x68, 0x71, 0xac, 0x0a, 0x91, 0xa3, 0x59, 0xee, 823 | 0x1a, 0xbc, 0x18, 0x0b, 0x94, 0x86, 0xb1, 0x37, 0xe0, 0x66, 0xe5, 0x21, 0x5f, 0x1b, 0x2f, 0x0b, 824 | 0x9b, 0x9b, 0x66, 0xfa, 0xdf, 0x92, 0xe3, 0x3d, 0x34, 0xca, 0xbd, 0x07, 0x7f, 0x76, 0xbe, 0xa3, 825 | 0xa6, 0xc9, 0x18, 0x1f, 0x95, 0x7c, 0x07, 0xed, 0x9b, 0xe2, 0x50, 0xaf, 0x43, 0xc3, 0xb7, 0xf8, 826 | 0x8b, 0xa7, 0x3f, 0xb7, 0x93, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x36, 0xe7, 0xb1, 0xde, 0x7d, 827 | 0x05, 0x00, 0x00, 828 | } 829 | -------------------------------------------------------------------------------- /cache/cache.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cache; 4 | 5 | service Cache { 6 | // streams cache request/response 7 | rpc Stream(stream CacheRequest) returns (stream CacheResponse) {} 8 | // single cache request/response 9 | rpc Call(CacheRequest) returns (CacheResponse) {} 10 | // convenience calls for all cache operations. these 11 | // just add the Operation and forward on to Call 12 | rpc Set(CacheRequest) returns (CacheResponse) {} 13 | rpc Cas(CacheRequest) returns (CacheResponse) {} 14 | rpc Get(CacheRequest) returns (CacheResponse) {} 15 | rpc Gets(CacheRequest) returns (CacheResponse) {} 16 | rpc Add(CacheRequest) returns (CacheResponse) {} 17 | rpc Replace(CacheRequest) returns (CacheResponse) {} 18 | rpc Delete(CacheRequest) returns (CacheResponse) {} 19 | rpc Touch(CacheRequest) returns (CacheResponse) {} 20 | rpc Append(CacheRequest) returns (CacheResponse) {} 21 | rpc Prepend(CacheRequest) returns (CacheResponse) {} 22 | rpc Increment(CacheRequest) returns (CacheResponse) {} 23 | rpc Decrement(CacheRequest) returns (CacheResponse) {} 24 | rpc FlushAll(CacheRequest) returns (CacheResponse) {} 25 | } 26 | 27 | // CacheItem encapsulates any in/out cache values into a single message 28 | // structure. Some values may not be pertinent in some situations (i.e. you 29 | // can't set the cas). 30 | message CacheItem { 31 | string key = 1; 32 | bytes value = 2; 33 | uint64 ttl = 3; 34 | uint64 cas = 4; 35 | } 36 | 37 | message CacheRequest { 38 | enum Operation { 39 | NOOP = 0; 40 | SET = 1; 41 | CAS = 2; 42 | GET = 3; 43 | GETS = 4; 44 | ADD = 5; 45 | REPLACE = 6; 46 | DELETE = 7; 47 | TOUCH = 8; 48 | APPEND = 9; 49 | PREPEND = 10; 50 | INCREMENT = 11; 51 | DECREMENT = 12; 52 | FLUSHALL = 13; 53 | } 54 | 55 | Operation operation = 1; 56 | CacheItem item = 2; 57 | bytes append = 3; 58 | bytes prepend = 4; 59 | uint64 increment = 5; 60 | uint64 decrement = 6; 61 | } 62 | 63 | message CacheResponse { 64 | CacheItem item = 1; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /lru/lru.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Package lru implements an LRU+TTL caching library that (mostly) follows the 4 | functions offered by memcached. It was initially based on groupcache/lru and 5 | evolved into a more fully featured library to support the back end caching 6 | needs of grpc-cache. Cache items are keyed with strings and valued with 7 | []byte. While interface{} values might be a bit more flexible, []byte was 8 | chosen for a few reasons. First, []byte aligns better with protobuf's byte 9 | type, though this library should be useful outside of that context. Second, 10 | it's relatively easy to encode/decode Go types into a []byte, and in fact 11 | common complex encoded types in Go use []byte natively (encoding/json, 12 | encoding/gob, etc). Third, the update functions (Increment/Decrement and 13 | Append/Prepend) are easier to write and test without resorting to 14 | reflection. For convenience, helpers are provided for encoding and decoding 15 | uint64 to/from []byte for counters. 16 | 17 | This library employs two cache strategies: Least Recently Used (LRU) and Time 18 | To Live. LRU can be disabled globally by setting the maxEntries to 0: 19 | 20 | myCache := lru.New(0) 21 | 22 | TTL can be disabled on a per item basis by setting the ttl argument in the 23 | various set commands to 0: Otherwise, items will expire when the time.Duration 24 | 25 | myCache.Set("blah", []byte("whatever"), time.Minutes*20) 26 | 27 | has been reached and the item is accessed again ... note that timeouts are 28 | lazy, so items that are set and have timed out but aren't accessed will count 29 | towards the LRU max. 30 | 31 | Note that this library is not thread safe. Locking has been left up to the 32 | caller. This allows, for exammple, more efficient batch operations because the 33 | library functions operate on a single value, but the caller could lock around a 34 | set of operations. In addition, it allows the caller to use their preferred 35 | method for assuring safe concurrent access. And finally, if the library is used 36 | in a single-threaded situation, locking unecessary locking won't needlessly 37 | impact performance. All that said, the Cache type conveniently embeds 38 | sync.Mutex so that a per instance lock is easy to use: 39 | 40 | myCache.Lock() 41 | myCache.Set("thing", []byte("stuff"), 0) 42 | myCache.Unlock() 43 | 44 | Note also that all operations read from and potentially write to internal data 45 | structures, so locking in a concurrent environment is necessary, even for 46 | Get/Gets. 47 | 48 | */ 49 | package lru 50 | 51 | import ( 52 | "bytes" 53 | "container/list" 54 | "encoding/binary" 55 | "errors" 56 | "sync" 57 | "sync/atomic" 58 | "time" 59 | ) 60 | 61 | // Cache is an LRU+TTL cache 62 | type Cache struct { 63 | sync.Mutex 64 | maxEntries int 65 | evictionHandler EvictionHandler 66 | lruList *list.List 67 | cache map[string]*list.Element 68 | casID uint64 69 | } 70 | 71 | // entry represents a an entry in the cache. 72 | type entry struct { 73 | key string 74 | value []byte 75 | cas uint64 76 | ttl time.Duration 77 | createdAt time.Time 78 | } 79 | 80 | // EvictionReason encapsulates the reason for an eviction in an evictionHandler 81 | type EvictionReason int 82 | 83 | // String stringifies the EvictionReason 84 | func (e EvictionReason) String() string { 85 | switch e { 86 | case LRUEviction: 87 | return "LRUEviction" 88 | case TTLEviction: 89 | return "TTLEviction" 90 | } 91 | return "" 92 | } 93 | 94 | const ( 95 | // LRUEviction denotes an item was evicted via LRU 96 | LRUEviction = iota 97 | // TTLEviction denotes an item was evicted via TTL 98 | TTLEviction 99 | ) 100 | 101 | // EvictionHandler is an interface for implementing a function to be called 102 | // when an item is evicted from the cache. 103 | type EvictionHandler interface { 104 | HandleEviction(key string, value []byte, reason EvictionReason) 105 | } 106 | 107 | // EvictionHandlerFunc is a convenience type for EvictionHandler 108 | // implementations. 109 | type EvictionHandlerFunc func(key string, value []byte, reason EvictionReason) 110 | 111 | // HandleEviction implements the EvictionHandler interface. 112 | func (h EvictionHandlerFunc) HandleEviction(key string, value []byte, reason EvictionReason) { 113 | h(key, value, reason) 114 | } 115 | 116 | // ErrNotFound is the error returned when a value isn't found. 117 | var ErrNotFound = errors.New("item not found") 118 | 119 | // ErrExists is the error returned when an item exists. 120 | var ErrExists = errors.New("item exists") 121 | 122 | // New creates a new Cache and initializes the various internal items. 123 | func New(maxEntries int) *Cache { 124 | return &Cache{ 125 | maxEntries: maxEntries, 126 | lruList: list.New(), 127 | cache: make(map[string]*list.Element), 128 | casID: 0, 129 | } 130 | } 131 | 132 | // WithEvictionHandler attaches a callback that will be called whenever an item 133 | // is evicted from the cache. Note that this will be called for LRU or TTL 134 | // evictions but not for explicit calls to Delete. 135 | func (c *Cache) WithEvictionHandler(h EvictionHandler) *Cache { 136 | c.evictionHandler = h 137 | return c 138 | } 139 | 140 | // Set unconditionally sets the item, potentially overwriting a previous value 141 | // and moving the item to the top of the LRU. 142 | func (c *Cache) Set(key string, value []byte, ttl time.Duration) { 143 | // key already exists, update values and move to the front 144 | if ee, ok := c.cache[key]; ok { 145 | c.lruList.MoveToFront(ee) 146 | ee.Value.(*entry).value = value 147 | ee.Value.(*entry).ttl = ttl 148 | ee.Value.(*entry).createdAt = time.Now() 149 | ee.Value.(*entry).cas = c.nextCasID() 150 | return 151 | } 152 | // new entry: create, store and update the LRU 153 | ele := c.lruList.PushFront(&entry{ 154 | key: key, 155 | value: value, 156 | ttl: ttl, 157 | createdAt: time.Now(), 158 | cas: c.nextCasID(), 159 | }) 160 | c.cache[key] = ele 161 | if c.maxEntries != 0 && c.lruList.Len() > c.maxEntries { 162 | ele := c.lruList.Back() 163 | if ele != nil { 164 | if c.evictionHandler != nil { 165 | c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, LRUEviction) 166 | } 167 | c.removeElement(ele) 168 | } 169 | } 170 | } 171 | 172 | // Touch updates the item's eviction status (LRU and TTL if supplied) and CAS 173 | // ID without requiring the value. If the item doesn't already exist, it 174 | // returns an ErrNotFound. 175 | func (c *Cache) Touch(key string, ttl time.Duration) error { 176 | if ele, ok := c.cache[key]; ok { 177 | c.Set(key, ele.Value.(*entry).value, ttl) 178 | return nil 179 | } 180 | return ErrNotFound 181 | } 182 | 183 | // Add sets the item only if it doesn't already exist. 184 | func (c *Cache) Add(key string, value []byte, ttl time.Duration) error { 185 | if _, ok := c.cache[key]; !ok { 186 | c.Set(key, value, ttl) 187 | return nil 188 | } 189 | return ErrExists 190 | } 191 | 192 | // Replace only sets the item if it does already exist. 193 | func (c *Cache) Replace(key string, value []byte, ttl time.Duration) error { 194 | if _, ok := c.cache[key]; ok { 195 | c.Set(key, value, ttl) 196 | return nil 197 | } 198 | return ErrNotFound 199 | } 200 | 201 | // Cas is a "compare and swap" or "check and set" operation. It attempts to set 202 | // the key/value pair if a) the item already exists in the cache and 203 | // b) the item's current cas value matches the supplied argument. If the item doesn't exist, 204 | // it returns ErrNotFound, and if the item exists but has a different cas 205 | // value, it returns ErrExists. CAS is useful when multiple clients may be 206 | // operating on the same cached items and updates should only be applied by one 207 | // if a change hasn't occurred in the meantime by another. 208 | func (c *Cache) Cas(key string, value []byte, ttl time.Duration, cas uint64) error { 209 | if ele, ok := c.cache[key]; ok { 210 | if ele.Value.(*entry).cas == cas { 211 | c.Set(key, value, ttl) 212 | return nil 213 | } 214 | return ErrExists 215 | } 216 | return ErrNotFound 217 | } 218 | 219 | // getElement gets the raw cache entry from the map if it both exists and has 220 | // not yet expired. If the element is still valid, it moves it to the front of 221 | // the LRU list and returns the raw element. 222 | func (c *Cache) getElement(key string) *list.Element { 223 | if ele, hit := c.cache[key]; hit { 224 | if isExpired(ele) { 225 | if c.evictionHandler != nil { 226 | c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, TTLEviction) 227 | } 228 | c.removeElement(ele) 229 | return nil 230 | } 231 | c.lruList.MoveToFront(ele) 232 | return ele 233 | } 234 | return nil 235 | } 236 | 237 | // Get gets the value for the given key. 238 | func (c *Cache) Get(key string) ([]byte, error) { 239 | ele := c.getElement(key) 240 | if ele != nil { 241 | return ele.Value.(*entry).value, nil 242 | } 243 | return nil, ErrNotFound 244 | } 245 | 246 | // Gets gets the value for the given key and also returns the value's CAS ID. 247 | func (c *Cache) Gets(key string) ([]byte, uint64, error) { 248 | ele := c.getElement(key) 249 | if ele != nil { 250 | return ele.Value.(*entry).value, ele.Value.(*entry).cas, nil 251 | } 252 | return nil, 0, ErrNotFound 253 | } 254 | 255 | // Append appends the given value to the currently stored value for the key. If 256 | // the key doesn't currently exist (or has aged out) ErrNotFound is returned. 257 | func (c *Cache) Append(key string, value []byte, ttl time.Duration) error { 258 | ele := c.getElement(key) 259 | if ele != nil { 260 | newValue := append(ele.Value.(*entry).value, value...) 261 | c.Set(key, newValue, ttl) 262 | return nil 263 | } 264 | return ErrNotFound 265 | } 266 | 267 | // Prepend prepends the given value to the currently stored value for the key. If 268 | // the key doesn't currently exist (or has aged out) ErrNotFound is returned. 269 | func (c *Cache) Prepend(key string, value []byte, ttl time.Duration) error { 270 | ele := c.getElement(key) 271 | if ele != nil { 272 | newValue := append(value, ele.Value.(*entry).value...) 273 | c.Set(key, newValue, ttl) 274 | return nil 275 | } 276 | return ErrNotFound 277 | } 278 | 279 | // BytesToUint64 is a helper to convert a byte slice to a uint64. 280 | func BytesToUint64(b []byte) (uint64, error) { 281 | y, err := binary.ReadUvarint(bytes.NewReader(b)) 282 | if err != nil { 283 | return 0, err 284 | } 285 | return y, nil 286 | } 287 | 288 | // Uint64ToBytes is a helper to convert a uint64 to a byte slice. 289 | func Uint64ToBytes(n uint64) []byte { 290 | buf := make([]byte, binary.MaxVarintLen64) 291 | binary.PutUvarint(buf, n) 292 | return buf 293 | } 294 | 295 | // Increment increments the value of key by incrementBy. The value should be stored 296 | // as a uint64 converted to a []byte with Uint64ToBytes (or something 297 | // equivalent) or the behavior is undefined. 298 | func (c *Cache) Increment(key string, incrementBy uint64) error { 299 | ele := c.getElement(key) 300 | if ele != nil { 301 | n, err := BytesToUint64(ele.Value.(*entry).value) 302 | if err != nil { 303 | return err 304 | } 305 | 306 | n += incrementBy 307 | 308 | b := Uint64ToBytes(n) 309 | ele.Value.(*entry).value = b 310 | ele.Value.(*entry).cas = c.nextCasID() 311 | return nil 312 | 313 | } 314 | return ErrNotFound 315 | } 316 | 317 | // Decrement decrements the value of key by decrBy. The value should be stored 318 | // as a uint64 converted to a []byte with Uint64ToBytes (or something 319 | // equivalent) or the behavior is undefined. 320 | func (c *Cache) Decrement(key string, decrementBy uint64) error { 321 | ele := c.getElement(key) 322 | if ele != nil { 323 | n, err := BytesToUint64(ele.Value.(*entry).value) 324 | if err != nil { 325 | return err 326 | } 327 | 328 | n -= decrementBy 329 | 330 | b := Uint64ToBytes(n) 331 | ele.Value.(*entry).value = b 332 | ele.Value.(*entry).cas = c.nextCasID() 333 | return nil 334 | 335 | } 336 | return ErrNotFound 337 | } 338 | 339 | // Delete deletes the item from the cache. 340 | func (c *Cache) Delete(key string) { 341 | if ele, hit := c.cache[key]; hit { 342 | c.removeElement(ele) 343 | } 344 | } 345 | 346 | // FlushAll removes all items from the cache. 347 | func (c *Cache) FlushAll() { 348 | c.lruList = nil 349 | c.cache = nil 350 | } 351 | 352 | // nextCasId increments and returns the next cas id. 353 | func (c *Cache) nextCasID() uint64 { 354 | atomic.AddUint64(&c.casID, 1) 355 | return atomic.LoadUint64(&c.casID) 356 | } 357 | 358 | // isExpired returns true if the item exists and is expired, false otherwise. 359 | func isExpired(e *list.Element) bool { 360 | ttl := e.Value.(*entry).ttl 361 | if ttl == 0 { 362 | return false 363 | } 364 | 365 | createdAt := e.Value.(*entry).createdAt 366 | if time.Since(createdAt) > ttl { 367 | return true 368 | } 369 | return false 370 | } 371 | 372 | // removeElement unconditionally removed the element from the cache. 373 | func (c *Cache) removeElement(e *list.Element) { 374 | c.lruList.Remove(e) 375 | kv := e.Value.(*entry) 376 | delete(c.cache, kv.key) 377 | } 378 | -------------------------------------------------------------------------------- /lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestBasic(t *testing.T) { 11 | // create a cache with 3 max entries 12 | c := New(3) 13 | 14 | // set a single item. it should be there 15 | c.Set("foo", []byte("bar"), 0) 16 | if v, err := c.Get("foo"); err == nil { 17 | if bytes.Compare(v, []byte("bar")) != 0 { 18 | t.Fatalf("stored value not as expected: %s", v) 19 | } 20 | } 21 | 22 | // add the same key, it shouldn't replace the old value since it already exists 23 | err := c.Add("foo", []byte("notbar"), 0) 24 | if err != ErrExists { 25 | t.Fatal("expected ErrExists") 26 | } 27 | if v, err := c.Get("foo"); err == nil { 28 | if bytes.Compare(v, []byte("bar")) != 0 { 29 | t.Fatalf("foo should still be bar, not 'notbar'") 30 | } 31 | } 32 | 33 | // use replace, it should overwrite the old value 34 | err = c.Replace("foo", []byte("newbar"), 0) 35 | if err != nil { 36 | t.Fatal("got an unexpected error from Replace") 37 | } 38 | if v, err := c.Get("foo"); err == nil { 39 | if bytes.Compare(v, []byte("newbar")) != 0 { 40 | t.Fatalf("foo should now be 'newbar' but its %s", v) 41 | } 42 | } 43 | 44 | // try to replace something that doesn't exist, it shouldn't add anything 45 | err = c.Replace("yuk", []byte("doesn'tmatter"), 0) 46 | if err != ErrNotFound { 47 | t.Fatal("should have gotten a NotFound error from Replace") 48 | } 49 | if v, err := c.Get("yuk"); err != ErrNotFound { 50 | t.Fatalf("yuk shouldn't be there but it was %s", v) 51 | } 52 | 53 | // now push our first item "foo" off the LRU by adding three new items 54 | err = c.Add("bar", []byte("stuff"), 0) 55 | if err != nil { 56 | t.Fatal("Add returned an error") 57 | } 58 | err = c.Add("thing", []byte("other"), 0) 59 | if err != nil { 60 | t.Fatal("Add returned an error") 61 | } 62 | err = c.Add("another", []byte("thing"), 0) 63 | if err != nil { 64 | t.Fatal("Add returned an error") 65 | } 66 | 67 | if v, err := c.Get("foo"); err != ErrNotFound { 68 | t.Fatalf("'foo' should be gone now but it still returned a value: %s", v) 69 | } 70 | 71 | c.Delete("bar") 72 | if v, err := c.Get("bar"); err != ErrNotFound { 73 | t.Fatalf("'bar' should have been deleted: %s", v) 74 | } 75 | 76 | // clear the entire cache 77 | c.FlushAll() 78 | if v, err := c.Get("thing"); err != ErrNotFound { 79 | t.Fatalf("all items should have been flushed but found: %s", v) 80 | } 81 | } 82 | 83 | func TestCAS(t *testing.T) { 84 | c := New(5) 85 | 86 | // set an initial item 87 | c.Set("castest", []byte("stuff"), 0) 88 | 89 | // fetch it with gets and get the cas value 90 | v, n, err := c.Gets("castest") 91 | if err != nil { 92 | t.Fatal("error getting castest") 93 | } 94 | if n != 1 || bytes.Compare(v, []byte("stuff")) != 0 { 95 | t.Fatal("stored value doesn't look right") 96 | } 97 | 98 | // set something new using the cas value, should succeed 99 | err = c.Cas("castest", []byte("new stuff"), 0, n) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | // set something new using the wrong cas value, should fail 105 | err = c.Cas("castest", []byte("nope"), 0, 100) 106 | if err != ErrExists { 107 | t.Fatal(err) 108 | } 109 | 110 | // gets the value again, should be from the first attempt 111 | v, n, err = c.Gets("castest") 112 | if err != nil { 113 | t.Fatal("error getting castest") 114 | } 115 | if n != 2 || bytes.Compare(v, []byte("new stuff")) != 0 { 116 | t.Fatal("stored value doesn't look right") 117 | } 118 | 119 | err = c.Cas("castest", []byte("yup"), 0, 2) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | // gets the value again 125 | v, n, err = c.Gets("castest") 126 | if err != nil { 127 | t.Fatal("error getting castest") 128 | } 129 | if n != 3 || bytes.Compare(v, []byte("yup")) != 0 { 130 | t.Fatalf("stored value doesn't look right: %d %s", n, v) 131 | } 132 | 133 | err = c.Touch("castest", 0) 134 | if err != nil { 135 | t.Logf("touch of 'castest' failed: %s", err) 136 | } 137 | 138 | _, n, err = c.Gets("castest") 139 | if err != nil { 140 | t.Fatal("error getting castest") 141 | } 142 | if n != 4 { 143 | t.Fatalf("cas wasn't updated by touch: %d", n) 144 | } 145 | 146 | } 147 | 148 | func TestPrependAppend(t *testing.T) { 149 | c := New(0) 150 | 151 | c.Set("foo", []byte("bar"), 0) 152 | c.Set("yuk", []byte("woof"), 0) 153 | 154 | err := c.Append("foo", []byte("stuff"), 0) 155 | if err != nil { 156 | t.Fatal("got an error trying to append") 157 | } 158 | 159 | if v, err := c.Get("foo"); err != nil || bytes.Compare(v, []byte("barstuff")) != 0 { 160 | t.Fatalf("append failed: %s %s", v, err) 161 | } 162 | 163 | err = c.Prepend("yuk", []byte("things"), 0) 164 | if err != nil { 165 | t.Fatalf("got an error trying to prepend") 166 | } 167 | 168 | if v, err := c.Get("yuk"); err != nil || bytes.Compare(v, []byte("thingswoof")) != 0 { 169 | t.Fatalf("prepend failed: %s %s", v, err) 170 | } 171 | } 172 | 173 | func TestScratch(t *testing.T) { 174 | v := []byte("one") 175 | t.Log(string(append(v, []byte("two")...))) 176 | 177 | buf := make([]byte, binary.MaxVarintLen64) 178 | binary.PutUvarint(buf, 20) 179 | x, err := binary.ReadUvarint(bytes.NewReader(buf)) 180 | if err != nil { 181 | t.Fatal("oops") 182 | } 183 | if x != 20 { 184 | t.Fatal("doh") 185 | } 186 | c := New(2) 187 | c.Lock() 188 | c.Unlock() 189 | } 190 | 191 | func TestEvictionHandler(t *testing.T) { 192 | var x string 193 | c := New(2).WithEvictionHandler(EvictionHandlerFunc(func(key string, value []byte, reason EvictionReason) { 194 | t.Logf("evicted %s: %s", key, reason) 195 | x = key 196 | })) 197 | 198 | c.Set("first", []byte("gets evicted"), 0) 199 | c.Set("second", []byte("val"), time.Nanosecond*1) 200 | c.Set("third", []byte("val"), 0) 201 | if x != "first" { 202 | t.Fatal("expected 'first' to get evicted via LRU") 203 | } 204 | // call Get to force a ttl eviction 205 | v, err := c.Get("second") 206 | if err != ErrNotFound { 207 | t.Fatalf("found 'second', it should have been evicted: %s %s", err, v) 208 | } 209 | if x != "second" { 210 | t.Fatal("expected 'second' to get evicted via TTL") 211 | } 212 | } 213 | 214 | func TestBytesToUint64ToBytes(t *testing.T) { 215 | x := Uint64ToBytes(1 << 6) 216 | y, err := BytesToUint64(x) 217 | if err != nil { 218 | t.Fatalf("error converting bytes to uint64: %s", err) 219 | } 220 | if y != 64 { 221 | t.Fatalf("conversion didn't work as expected, y was %d", y) 222 | } 223 | } 224 | 225 | func TestIncrementDecrement(t *testing.T) { 226 | c := New(20) 227 | 228 | // set foo to 20 229 | r := Uint64ToBytes(20) 230 | c.Set("foo", r, 0) 231 | 232 | // increment foo by 20 233 | err := c.Increment("foo", 20) 234 | if err != nil { 235 | t.Fatalf("error trying to increment foo: %s", err) 236 | } 237 | 238 | // pull foo back out 239 | v, err := c.Get("foo") 240 | if err != nil { 241 | t.Fatalf("error getting 'foo': %s", err) 242 | } 243 | 244 | // convert the value 245 | y, err := BytesToUint64(v) 246 | if err != nil { 247 | t.Fatalf("error converting bytes to uint64: %s", err) 248 | } 249 | 250 | // and make sure it incremented 251 | if y != 40 { 252 | t.Fatalf("increment didn't work as expected, foo is %d", y) 253 | } 254 | 255 | // now derement it by 10 256 | err = c.Decrement("foo", 10) 257 | if err != nil { 258 | t.Fatalf("error trying to decrement foo: %s", err) 259 | } 260 | 261 | // pull foo back out 262 | x, err := c.Get("foo") 263 | if err != nil { 264 | t.Fatalf("error getting 'foo': %s", err) 265 | } 266 | 267 | // convert the value 268 | z, err := BytesToUint64(x) 269 | if err != nil { 270 | t.Fatalf("error converting bytes to uint64: %s", err) 271 | } 272 | 273 | // and make sure it decremented 274 | if z != 30 { 275 | t.Fatalf("decrement didn't work as expected, foo is %d", y) 276 | } 277 | } 278 | 279 | func TestTTL(t *testing.T) { 280 | c := New(5) 281 | 282 | c.Set("foo", []byte("bar"), time.Second*2) 283 | time.Sleep(time.Second * 1) 284 | 285 | if _, err := c.Get("foo"); err == ErrNotFound { 286 | t.Fatal("'foo' should still be there") 287 | } 288 | 289 | time.Sleep(time.Second * 1) 290 | if _, err := c.Get("foo"); err != ErrNotFound { 291 | t.Fatal("'foo' should have expired") 292 | } 293 | } 294 | 295 | func BenchmarkSet(b *testing.B) { 296 | c := New(4096) 297 | 298 | for i := 0; i < b.N; i++ { 299 | c.Set("foo", []byte("bench"), 0) 300 | } 301 | 302 | } 303 | 304 | func BenchmarkGet(b *testing.B) { 305 | c := New(4096) 306 | 307 | c.Set("foo", []byte("bench"), 0) 308 | for i := 0; i < b.N; i++ { 309 | c.Get("foo") 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/joshrotenberg/grpc-cache/server" 11 | ) 12 | 13 | var ( 14 | serverAddr string 15 | cacheMaxEntries int 16 | ) 17 | 18 | func init() { 19 | flag.StringVar(&serverAddr, "addr", "", "host:port to listen on") 20 | flag.IntVar(&cacheMaxEntries, "maxEntries", 0, "maxiumum cache entries") 21 | flag.Parse() 22 | } 23 | func main() { 24 | sigs := make(chan os.Signal, 1) 25 | 26 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 27 | 28 | s, err := server.New(serverAddr, cacheMaxEntries) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | s.Start() 33 | <-sigs 34 | s.Stop() 35 | } 36 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | pb "github.com/joshrotenberg/grpc-cache/cache" 10 | "golang.org/x/net/context" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | 15 | "github.com/joshrotenberg/grpc-cache/lru" 16 | ) 17 | 18 | // CacheServer encapsulates the cache server things. 19 | type CacheServer struct { 20 | cache *lru.Cache 21 | grpcServer *grpc.Server 22 | listener net.Listener 23 | } 24 | 25 | // NewWithListener returns a new instance of the server given an initialized listener and 26 | // maxEntries for the cache. 27 | func NewWithListener(listener net.Listener, maxEntries int) *CacheServer { 28 | grpcServer := grpc.NewServer() 29 | server := CacheServer{ 30 | cache: lru.New(maxEntries), 31 | grpcServer: grpcServer, 32 | listener: listener, 33 | } 34 | 35 | pb.RegisterCacheServer(grpcServer, &server) 36 | return &server 37 | } 38 | 39 | // New returns a new instance of the server. It takes host:port and maxEntries arguments, 40 | // which defines the max number of entries allowed in the cache. Set to 0 for unlimited. 41 | func New(address string, maxEntries int) (*CacheServer, error) { 42 | listener, err := net.Listen("tcp", address) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return NewWithListener(listener, maxEntries), nil 47 | } 48 | 49 | // Start starts the cache server. 50 | func (s *CacheServer) Start() { 51 | go func() { 52 | if err := s.grpcServer.Serve(s.listener); err != nil { 53 | log.Fatalf("failed to serve: %v", err) 54 | } 55 | }() 56 | } 57 | 58 | // Stop tries to gracefull stop the server. 59 | func (s *CacheServer) Stop() { 60 | s.grpcServer.GracefulStop() 61 | } 62 | 63 | func cacheError(err error, op pb.CacheRequest_Operation, key string) error { 64 | switch err { 65 | case lru.ErrNotFound: 66 | return status.Errorf(codes.NotFound, "%s error: '%s' not found", op, key) 67 | case lru.ErrExists: 68 | return status.Errorf(codes.AlreadyExists, "%s error: '%s' exists", op, key) 69 | } 70 | return err 71 | } 72 | 73 | func cacheResponse(err error, op pb.CacheRequest_Operation, item *pb.CacheItem) (*pb.CacheResponse, error) { 74 | if err != nil { 75 | return nil, cacheError(err, op, item.Key) 76 | } 77 | response := &pb.CacheResponse{ 78 | Item: item, 79 | } 80 | return response, err 81 | } 82 | 83 | // Stream ... 84 | func (s *CacheServer) Stream(stream pb.Cache_StreamServer) error { 85 | for { 86 | in, err := stream.Recv() 87 | if err == io.EOF { 88 | return nil 89 | } 90 | if err != nil { 91 | return err 92 | } 93 | log.Printf("i got %v", in) 94 | resp, err := s.Call(stream.Context(), in) 95 | if err != nil { 96 | return err 97 | } 98 | if err := stream.Send(resp); err != nil { 99 | return err 100 | } 101 | } 102 | } 103 | 104 | // Call calls the cache operation in in.Operation 105 | func (s *CacheServer) Call(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 106 | 107 | var err error 108 | s.cache.Lock() 109 | defer s.cache.Unlock() 110 | 111 | switch in.Operation { 112 | case pb.CacheRequest_NOOP: 113 | return cacheResponse(nil, in.Operation, in.Item) 114 | case pb.CacheRequest_SET: 115 | s.cache.Set(in.Item.Key, in.Item.Value, time.Duration(in.Item.Ttl)*time.Second) 116 | return cacheResponse(nil, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 117 | case pb.CacheRequest_CAS: 118 | err = s.cache.Cas(in.Item.Key, in.Item.Value, time.Duration(in.Item.Ttl)*time.Second, uint64(in.Item.Cas)) 119 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 120 | case pb.CacheRequest_GET: 121 | value, err := s.cache.Get(in.Item.Key) 122 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key, Value: value}) 123 | case pb.CacheRequest_GETS: 124 | value, cas, err := s.cache.Gets(in.Item.Key) 125 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key, Value: value, Cas: cas}) 126 | case pb.CacheRequest_ADD: 127 | err = s.cache.Add(in.Item.Key, in.Item.Value, time.Duration(in.Item.Ttl)*time.Second) 128 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 129 | case pb.CacheRequest_REPLACE: 130 | err = s.cache.Replace(in.Item.Key, in.Item.Value, time.Duration(in.Item.Ttl)*time.Second) 131 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 132 | case pb.CacheRequest_DELETE: 133 | s.cache.Delete(in.Item.Key) 134 | return cacheResponse(nil, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 135 | case pb.CacheRequest_TOUCH: 136 | err = s.cache.Touch(in.Item.Key, time.Duration(in.Item.Ttl)*time.Second) 137 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 138 | case pb.CacheRequest_APPEND: 139 | err = s.cache.Append(in.Item.Key, in.Append, time.Duration(in.Item.Ttl)*time.Second) 140 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 141 | case pb.CacheRequest_PREPEND: 142 | err = s.cache.Prepend(in.Item.Key, in.Prepend, time.Duration(in.Item.Ttl)*time.Second) 143 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 144 | case pb.CacheRequest_INCREMENT: 145 | err = s.cache.Increment(in.Item.Key, in.Increment) 146 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 147 | case pb.CacheRequest_DECREMENT: 148 | err = s.cache.Decrement(in.Item.Key, in.Decrement) 149 | return cacheResponse(err, in.Operation, &pb.CacheItem{Key: in.Item.Key}) 150 | case pb.CacheRequest_FLUSHALL: 151 | s.cache.FlushAll() 152 | return cacheResponse(nil, in.Operation, nil) 153 | default: 154 | return nil, status.Errorf(codes.Unimplemented, "unrecognized cache command %d", in.Operation) 155 | } 156 | 157 | } 158 | 159 | // Set stores a key/value pair in the cache. 160 | func (s *CacheServer) Set(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 161 | in.Operation = pb.CacheRequest_SET 162 | return s.Call(ctx, in) 163 | } 164 | 165 | // Cas does a "check and set" or "compare and swap" operation. If the given key exists 166 | // and the currently stored CAS value matches the one supplied, the value is stored. 167 | func (s *CacheServer) Cas(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 168 | in.Operation = pb.CacheRequest_CAS 169 | return s.Call(ctx, in) 170 | } 171 | 172 | // Get gets a key/value pair from the cache. 173 | func (s *CacheServer) Get(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 174 | in.Operation = pb.CacheRequest_GET 175 | return s.Call(ctx, in) 176 | } 177 | 178 | // Gets gets a key/value pair from the cache and includes its CAS value. 179 | func (s *CacheServer) Gets(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 180 | in.Operation = pb.CacheRequest_GETS 181 | return s.Call(ctx, in) 182 | } 183 | 184 | // Add adds a key/value pair to the cache. 185 | func (s *CacheServer) Add(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 186 | in.Operation = pb.CacheRequest_ADD 187 | return s.Call(ctx, in) 188 | } 189 | 190 | // Replace replaces a key/value pair in the cache. 191 | func (s *CacheServer) Replace(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 192 | in.Operation = pb.CacheRequest_REPLACE 193 | return s.Call(ctx, in) 194 | } 195 | 196 | // Delete deletes a key/value pair from the cache. 197 | func (s *CacheServer) Delete(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 198 | in.Operation = pb.CacheRequest_DELETE 199 | return s.Call(ctx, in) 200 | } 201 | 202 | // Touch updates the key/value pair's cas and ttl. 203 | func (s *CacheServer) Touch(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 204 | in.Operation = pb.CacheRequest_TOUCH 205 | return s.Call(ctx, in) 206 | } 207 | 208 | // Append appends the value to the current value for the key. 209 | func (s *CacheServer) Append(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 210 | in.Operation = pb.CacheRequest_APPEND 211 | return s.Call(ctx, in) 212 | } 213 | 214 | // Prepend prepends the value to the current value for the key. 215 | func (s *CacheServer) Prepend(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 216 | in.Operation = pb.CacheRequest_PREPEND 217 | return s.Call(ctx, in) 218 | } 219 | 220 | // Increment increments the value for key. 221 | func (s *CacheServer) Increment(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 222 | in.Operation = pb.CacheRequest_INCREMENT 223 | return s.Call(ctx, in) 224 | } 225 | 226 | // Decrement decrements the value for the given key. 227 | func (s *CacheServer) Decrement(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 228 | in.Operation = pb.CacheRequest_DECREMENT 229 | return s.Call(ctx, in) 230 | } 231 | 232 | // FlushAll deletes all key/value pairs from the cache. 233 | func (s *CacheServer) FlushAll(ctx context.Context, in *pb.CacheRequest) (*pb.CacheResponse, error) { 234 | in.Operation = pb.CacheRequest_FLUSHALL 235 | return s.Call(ctx, in) 236 | } 237 | -------------------------------------------------------------------------------- /server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "testing" 11 | 12 | "reflect" 13 | 14 | pb "github.com/joshrotenberg/grpc-cache/cache" 15 | "github.com/joshrotenberg/grpc-cache/lru" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/codes" 18 | "google.golang.org/grpc/grpclog" 19 | "google.golang.org/grpc/status" 20 | ) 21 | 22 | // ripped off from net/http/httptest/server.go 23 | func newLocalListener() net.Listener { 24 | l, err := net.Listen("tcp", "127.0.0.1:0") 25 | if err != nil { 26 | if l, err = net.Listen("tcp6", "[::1]:0"); err != nil { 27 | panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) 28 | } 29 | } 30 | return l 31 | } 32 | 33 | func testSetup(maxEntries int) pb.CacheClient { 34 | listener := newLocalListener() 35 | s2 := NewWithListener(listener, maxEntries) 36 | s2.Start() 37 | 38 | conn, err := grpc.Dial(listener.Addr().String(), grpc.WithInsecure()) 39 | if err != nil { 40 | log.Fatalf("error connecting to server: %s", err) 41 | } 42 | 43 | cc := pb.NewCacheClient(conn) 44 | return cc 45 | } 46 | 47 | func createCacheItem(key string, value string, ttl uint64) *pb.CacheItem { 48 | item := &pb.CacheItem{ 49 | Key: key, 50 | Value: []byte(value), 51 | Ttl: ttl, 52 | } 53 | return item 54 | } 55 | 56 | func createCacheRequest(operation pb.CacheRequest_Operation, item *pb.CacheItem) *pb.CacheRequest { 57 | 58 | return &pb.CacheRequest{Operation: operation, Item: item} 59 | } 60 | 61 | func testSet(t *testing.T, cc pb.CacheClient, key string, value string) *pb.CacheResponse { 62 | setRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: key, Value: []byte(value)}} 63 | setResponse, err := cc.Set(context.Background(), setRequest) 64 | if err != nil { 65 | t.Fatalf("error setting item: %s", err) 66 | } 67 | return setResponse 68 | } 69 | 70 | func testGet(t *testing.T, cc pb.CacheClient, key string, expectedValue string, expectedCode codes.Code) *pb.CacheResponse { 71 | getRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: key}} 72 | getResponse, err := cc.Get(context.Background(), getRequest) 73 | if err != nil { 74 | status, ok := status.FromError(err) 75 | if !ok { 76 | t.Fatalf("unknown error: %v", err) 77 | } 78 | if status.Code() != expectedCode { 79 | t.Fatalf("error codes do not match: %v", status) 80 | } 81 | } else { 82 | if string(getResponse.Item.Value) != expectedValue { 83 | t.Fatalf("get: got %s, expected %s", getResponse.Item.Value, expectedValue) 84 | } 85 | return getResponse 86 | } 87 | return nil 88 | } 89 | 90 | func TestCache(t *testing.T) { 91 | 92 | cc := testSetup(20) 93 | //defer s.GracefulStop() 94 | 95 | setRes := testSet(t, cc, "foo", "bar") 96 | t.Log(setRes) 97 | testGet(t, cc, "foo", "bar", codes.OK) 98 | 99 | } 100 | func TestSet(t *testing.T) { 101 | cc := testSetup(20) 102 | //defer s.GracefulStop() 103 | 104 | setRequest := createCacheRequest(pb.CacheRequest_SET, &pb.CacheItem{Key: "foo", Value: []byte("bar")}) 105 | _, err := cc.Set(context.Background(), setRequest) 106 | if err != nil { 107 | t.Fatalf("error setting item: %s", err) 108 | } 109 | testGet(t, cc, "foo", "bar", codes.OK) 110 | getRequest := createCacheRequest(pb.CacheRequest_GET, &pb.CacheItem{Key: "foo"}) 111 | getResponse, err := cc.Get(context.Background(), getRequest) 112 | if err != nil { 113 | t.Fatalf("error getting item: %s", err) 114 | } 115 | item := getResponse.Item 116 | if bytes.Compare(item.Value, []byte("bar")) != 0 { 117 | t.Fatalf("cache item value wasn't as expected; got %s, expected %s", item.Value, []byte("bar")) 118 | } 119 | } 120 | 121 | func TestCAS(t *testing.T) { 122 | 123 | cc := testSetup(20) 124 | //defer s.GracefulStop() 125 | 126 | // set something initially 127 | setRequest := createCacheRequest(pb.CacheRequest_SET, &pb.CacheItem{Key: "foo", Value: []byte("bar")}) 128 | _, err := cc.Set(context.Background(), setRequest) 129 | if err != nil { 130 | t.Fatalf("error setting item: %s", err) 131 | } 132 | 133 | // now get it along with the cas value 134 | getsRequest := createCacheRequest(pb.CacheRequest_GETS, &pb.CacheItem{Key: "foo"}) 135 | getsResponse, err := cc.Gets(context.Background(), getsRequest) 136 | if err != nil { 137 | t.Fatalf("error getting item: %s", err) 138 | } 139 | 140 | item := getsResponse.Item 141 | if bytes.Compare(item.Value, []byte("bar")) != 0 { 142 | t.Fatalf("cache item value wasn't as expected; got %s, expected %s", item.Value, []byte("bar")) 143 | } 144 | // this is the first thing we set in this cache so we know the cas should be 1 145 | if item.Cas != 1 { 146 | t.Fatalf("cas should have been 1 but it was %d", item.Cas) 147 | } 148 | 149 | // set something again with the same key and provide the cas value 150 | casRequest := createCacheRequest(pb.CacheRequest_CAS, &pb.CacheItem{Key: "foo", Value: []byte("bluh"), Cas: 1}) 151 | casResponse, err := cc.Cas(context.Background(), casRequest) 152 | if err != nil { 153 | t.Fatalf("error CASing item: %s", err) 154 | } 155 | item = casResponse.Item 156 | 157 | // pull it back out again 158 | getsRequest = createCacheRequest(pb.CacheRequest_GETS, &pb.CacheItem{Key: "foo"}) 159 | getsResponse, err = cc.Gets(context.Background(), getsRequest) 160 | if err != nil { 161 | t.Fatalf("error getting item: %s", err) 162 | } 163 | 164 | item = getsResponse.Item 165 | if bytes.Compare(item.Value, []byte("bluh")) != 0 { 166 | t.Fatalf("cache item value wasn't as expected; got %s, expected %s", item.Value, []byte("bluh")) 167 | } 168 | // the cas should have been incremented 169 | if item.Cas != 2 { 170 | t.Fatalf("cas should have been 2 but it was %d", item.Cas) 171 | } 172 | 173 | // now try to set something again with the key but provide a cas that definitely isn't right. the set should fail 174 | casRequest = createCacheRequest(pb.CacheRequest_CAS, &pb.CacheItem{Key: "foo", Value: []byte("nope"), Cas: 9}) 175 | casResponse, err = cc.Cas(context.Background(), casRequest) 176 | if err == nil { 177 | t.Fatal("cas shoud have failed") 178 | } 179 | } 180 | 181 | func TestAdd(t *testing.T) { 182 | 183 | cc := testSetup(20) 184 | //defer s.Stop() 185 | 186 | addRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo", Value: []byte("bar")}} 187 | _, err := cc.Add(context.Background(), addRequest) 188 | if err != nil { 189 | t.Fatalf("error adding item: %s", err) 190 | } 191 | 192 | addRequest = createCacheRequest(pb.CacheRequest_ADD, &pb.CacheItem{Key: "foo", Value: []byte("bar")}) 193 | _, err = cc.Add(context.Background(), addRequest) 194 | if err == nil { 195 | t.Fatal("add should have failed") 196 | } 197 | } 198 | 199 | func TestReplace(t *testing.T) { 200 | cc := testSetup(20) 201 | //defer s.GracefulStop() 202 | replaceRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo", Value: []byte("bar")}} 203 | _, err := cc.Replace(context.Background(), replaceRequest) 204 | 205 | if err == nil { 206 | t.Fatalf("expected an error trying to replace an item that doesn't exist") 207 | } 208 | testSet(t, cc, "foo", "bar") 209 | replaceRequest = &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo", Value: []byte("rar")}} 210 | _, err = cc.Replace(context.Background(), replaceRequest) 211 | if err != nil { 212 | t.Fatalf("got an error trying to replace an item that should exist: %s", err) 213 | } 214 | testGet(t, cc, "foo", "rar", codes.OK) 215 | } 216 | 217 | func TestDelete(t *testing.T) { 218 | cc := testSetup(20) 219 | testSet(t, cc, "doof", "cha") 220 | testGet(t, cc, "doof", "cha", codes.OK) 221 | 222 | deleteRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "doof"}} 223 | _, err := cc.Delete(context.Background(), deleteRequest) 224 | if err != nil { 225 | t.Fatalf("got an error deleting an item: %s", err) 226 | } 227 | 228 | testGet(t, cc, "doof", "", codes.NotFound) 229 | } 230 | 231 | func TestAppend(t *testing.T) { 232 | cc := testSetup(20) 233 | testSet(t, cc, "doof", "cha") 234 | appendRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "doof"}, Append: []byte("yeah")} 235 | cc.Append(context.Background(), appendRequest) 236 | testGet(t, cc, "doof", "chayeah", codes.OK) 237 | } 238 | 239 | func TestPrepend(t *testing.T) { 240 | cc := testSetup(20) 241 | testSet(t, cc, "doof", "cha") 242 | prependRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "doof"}, Prepend: []byte("yeah")} 243 | cc.Prepend(context.Background(), prependRequest) 244 | testGet(t, cc, "doof", "yeahcha", codes.OK) 245 | } 246 | 247 | func TestIncrement(t *testing.T) { 248 | cc := testSetup(20) 249 | v := lru.Uint64ToBytes(20) 250 | setRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo", Value: v}} 251 | _, err := cc.Set(context.Background(), setRequest) 252 | if err != nil { 253 | t.Fatal("error setting value") 254 | } 255 | getRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}} 256 | getResponse, err := cc.Get(context.Background(), getRequest) 257 | 258 | v2, err := lru.BytesToUint64(getResponse.Item.Value) 259 | if err != nil { 260 | t.Fatalf("error converting value: %s", err) 261 | } 262 | if v2 != 20 { 263 | t.Fatalf("value wasn't as expected: %d != 20", v2) 264 | } 265 | 266 | incrementRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}, Increment: 23} 267 | cc.Increment(context.Background(), incrementRequest) 268 | 269 | getRequest = &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}} 270 | getResponse, err = cc.Get(context.Background(), getRequest) 271 | v3, err := lru.BytesToUint64(getResponse.Item.Value) 272 | if err != nil { 273 | t.Fatalf("error converting value: %s", err) 274 | } 275 | if v3 != 43 { 276 | t.Fatalf("value wasn't as expected: %d != 43", v3) 277 | } 278 | } 279 | 280 | func TestDecrement(t *testing.T) { 281 | cc := testSetup(20) 282 | v := lru.Uint64ToBytes(43) 283 | setRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo", Value: v}} 284 | _, err := cc.Set(context.Background(), setRequest) 285 | if err != nil { 286 | t.Fatal("error setting value") 287 | } 288 | getRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}} 289 | getResponse, err := cc.Get(context.Background(), getRequest) 290 | v2, err := lru.BytesToUint64(getResponse.Item.Value) 291 | if err != nil { 292 | t.Fatalf("error converting value: %s", err) 293 | } 294 | if v2 != 43 { 295 | t.Fatalf("value wasn't as expected: %d != 43", v2) 296 | } 297 | 298 | decrementRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}, Decrement: 23} 299 | cc.Decrement(context.Background(), decrementRequest) 300 | 301 | getRequest = &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}} 302 | getResponse, err = cc.Get(context.Background(), getRequest) 303 | v3, err := lru.BytesToUint64(getResponse.Item.Value) 304 | if err != nil { 305 | t.Fatalf("error converting value: %s", err) 306 | } 307 | if v3 != 20 { 308 | t.Fatalf("value wasn't as expected: %d != 20", v3) 309 | } 310 | } 311 | 312 | func TestFlushAll(t *testing.T) { 313 | cc := testSetup(20) 314 | testSet(t, cc, "foo", "bar") 315 | testSet(t, cc, "boo", "far") 316 | 317 | flushAllRequest := &pb.CacheRequest{} 318 | flushAllResponse, err := cc.FlushAll(context.Background(), flushAllRequest) 319 | if err != nil { 320 | t.Fatalf("flush all failed: %v", err) 321 | } 322 | t.Log(flushAllResponse) 323 | } 324 | 325 | func TestNoop(t *testing.T) { 326 | cc := testSetup(0) 327 | noopItem := &pb.CacheItem{Key: "doesn't matter", Value: []byte("stuff")} 328 | // if no operation is specified (either through CacheRequest.Operation in the request or 329 | // via the wrapper call) the call will simply return the supplied cache item 330 | noopRequest := &pb.CacheRequest{Item: noopItem} 331 | noopResponse, err := cc.Call(context.Background(), noopRequest) 332 | if err != nil { 333 | t.Fatalf("noop request failed: %v", err) 334 | } 335 | if reflect.DeepEqual(noopResponse.GetItem(), noopItem) != true { 336 | t.Fatal("items didn't match") 337 | } 338 | } 339 | 340 | func TestStream(t *testing.T) { 341 | cc := testSetup(20) 342 | 343 | stream, err := cc.Stream(context.Background()) 344 | if err != nil { 345 | t.Fatalf("error acquiring stream: %v", err) 346 | } 347 | t.Log(stream) 348 | waitc := make(chan struct{}) 349 | go func() { 350 | for { 351 | in, err := stream.Recv() 352 | if err == io.EOF { 353 | // read done. 354 | close(waitc) 355 | return 356 | } 357 | if err != nil { 358 | grpclog.Fatalf("Failed to receive a note : %v", err) 359 | } 360 | t.Logf("doof %v", in) 361 | } 362 | }() 363 | 364 | setRequest := &pb.CacheRequest{Item: &pb.CacheItem{Key: "foo"}, Operation: pb.CacheRequest_SET} 365 | stream.Send(setRequest) 366 | stream.CloseSend() 367 | <-waitc 368 | } 369 | --------------------------------------------------------------------------------