This commit is contained in:
23
.drone.yml
Normal file
23
.drone.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: redis
|
||||||
|
image: redis
|
||||||
|
- name: memcache
|
||||||
|
image: memcached
|
||||||
|
|
||||||
|
environment:
|
||||||
|
FDEVS_CACHE_REDIS_HOST: redis:6379
|
||||||
|
FDEVS_CACHE_MEMCACHE_HOST: memcache:11211
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go test ./...
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
image: golangci/golangci-lint:v1.39
|
||||||
|
commands:
|
||||||
|
- golangci-lint run
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ---> Go
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
43
.golangci.yml
Normal file
43
.golangci.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- exhaustivestruct
|
||||||
|
- maligned
|
||||||
|
- interfacer
|
||||||
|
- scopelint
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
|
- path: test/*
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) 2020 go-4devs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
378
README.md
Normal file
378
README.md
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
# cache
|
||||||
|
|
||||||
|
[](https://drone.gitoa.ru/go-4devs/cache)
|
||||||
|
[](https://goreportcard.com/report/gitoa.ru/go-4devs/cache)
|
||||||
|
[](http://godoc.org/gitoa.ru/go-4devs/cache)
|
||||||
|
|
||||||
|
## Benchmark cache
|
||||||
|
|
||||||
|
`go test -v -timeout 25m -cpu 1,2,4,8,16 -benchmem -run=^$ -bench . bench_test.go`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
goos: darwin
|
||||||
|
goarch: amd64
|
||||||
|
BenchmarkCacheGetStruct
|
||||||
|
BenchmarkCacheGetStruct/encoding_json
|
||||||
|
BenchmarkCacheGetStruct/encoding_json 1519932 783 ns/op 320 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_json-2 1478414 780 ns/op 320 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_json-4 1353025 916 ns/op 320 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_json-8 1284042 839 ns/op 320 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_json-16 1422788 848 ns/op 320 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob 83661 15323 ns/op 6944 B/op 180 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob-2 81745 14407 ns/op 6944 B/op 180 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob-4 73537 15142 ns/op 6944 B/op 180 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob-8 85412 14494 ns/op 6944 B/op 180 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding_gob-16 75748 15219 ns/op 6944 B/op 180 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map
|
||||||
|
BenchmarkCacheGetStruct/map 6162325 199 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-2 5740689 195 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-4 6018531 200 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-8 5452492 210 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-16 5933622 202 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map_shards
|
||||||
|
BenchmarkCacheGetStruct/map_shards 5299807 230 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map_shards-2 5087726 238 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map_shards-4 4990490 243 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map_shards-8 4899127 225 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map_shards-16 5229320 233 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto
|
||||||
|
BenchmarkCacheGetStruct/ristretto 5511872 227 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-2 4664298 257 ns/op 103 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-4 4524751 265 ns/op 103 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-8 4425381 260 ns/op 103 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-16 4649698 258 ns/op 103 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru
|
||||||
|
BenchmarkCacheGetStruct/lru 4730811 250 ns/op 144 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-2 4627194 252 ns/op 144 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-4 4627082 257 ns/op 144 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-8 4755622 252 ns/op 144 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-16 4717584 250 ns/op 144 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_json
|
||||||
|
BenchmarkCacheGetStruct/redis_json 572 2132479 ns/op 9848 B/op 34 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_json-2 565 2161113 ns/op 9848 B/op 34 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_json-4 543 2183219 ns/op 9848 B/op 34 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_json-8 531 2148630 ns/op 9848 B/op 34 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_json-16 544 2212659 ns/op 9848 B/op 34 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_gob
|
||||||
|
BenchmarkCacheGetStruct/redis_gob 553 2206583 ns/op 16504 B/op 208 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_gob-2 549 2256638 ns/op 16505 B/op 208 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_gob-4 540 2230342 ns/op 16504 B/op 208 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_gob-8 537 2178895 ns/op 16504 B/op 208 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis_gob-16 541 2206298 ns/op 16504 B/op 208 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_json
|
||||||
|
BenchmarkCacheGetStruct/memcache_json 1352 882575 ns/op 560 B/op 16 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_json-2 1332 869724 ns/op 560 B/op 16 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_json-4 1326 824555 ns/op 561 B/op 16 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_json-8 1375 880741 ns/op 562 B/op 16 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_json-16 1346 872861 ns/op 563 B/op 16 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob 1431 828348 ns/op 7216 B/op 190 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob-2 1266 875339 ns/op 7216 B/op 190 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob-4 1327 908142 ns/op 7218 B/op 190 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob-8 1286 840878 ns/op 7219 B/op 190 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache_gob-16 1540 797765 ns/op 7220 B/op 190 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark providers
|
||||||
|
|
||||||
|
`go test -v -timeout 25m -cpu 1,2,4,8,16 -benchmem -run=^$ -bench . ./provider/bench_provider_test.go`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
goos: darwin
|
||||||
|
goarch: amd64
|
||||||
|
BenchmarkCacheGetRandomKeyString
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding 3100226 389 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding-2 3142849 379 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding-4 3118212 379 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding-8 3064170 387 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/encoding-16 3128031 384 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/map
|
||||||
|
BenchmarkCacheGetRandomKeyString/map 7342993 157 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/map-2 7268864 158 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/map-4 7233045 162 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/map-8 7393652 159 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/map-16 7463053 159 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard 3330136 351 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard-2 3518775 335 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard-4 3477537 336 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard-8 3514064 335 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/shard-16 3412119 341 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru 5013633 249 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru-2 4871456 247 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru-4 4786940 238 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru-8 4721556 238 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/lru-16 4870622 241 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto 5569208 205 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto-2 3892068 295 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto-4 4490196 266 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto-8 4381441 266 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/ristretto-16 4185096 273 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache 1492 811587 ns/op 528 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache-2 1400 840429 ns/op 528 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache-4 1381 793654 ns/op 528 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache-8 1455 826461 ns/op 530 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/memcache-16 1380 803712 ns/op 532 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis 540 2908289 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis-2 514 2287030 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis-4 542 2195917 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis-8 536 2209508 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/redis-16 544 2275867 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble 672912 1801 ns/op 1408 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble-2 773318 1691 ns/op 1408 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble-4 729020 1556 ns/op 1408 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble-8 778066 1491 ns/op 1408 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyString/pebble-16 838596 1441 ns/op 1408 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding 2825020 410 ns/op 207 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding-2 2932910 409 ns/op 207 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding-4 2837827 408 ns/op 207 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding-8 2842040 418 ns/op 207 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/encoding-16 2866555 409 ns/op 207 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map 7312549 150 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map-2 7884612 150 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map-4 7450554 158 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map-8 7471407 156 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/map-16 7469587 158 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard 6709964 187 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard-2 6430581 183 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard-4 6375858 187 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard-8 6399346 180 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/shard-16 6580282 175 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru 5183596 225 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru-2 5217847 220 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru-4 5078146 223 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru-8 4722044 225 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/lru-16 4989286 224 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto 6920838 169 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto-2 4763511 216 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto-4 5163074 220 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto-8 5133212 220 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/ristretto-16 5089780 219 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache 1332 820272 ns/op 544 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache-2 1408 840124 ns/op 544 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache-4 1443 809845 ns/op 544 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache-8 1449 832162 ns/op 545 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/memcache-16 1333 855560 ns/op 547 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis 525 2211523 ns/op 9767 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis-2 542 2146253 ns/op 9767 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis-4 531 2271602 ns/op 9767 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis-8 522 2273678 ns/op 9767 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/redis-16 552 2180911 ns/op 9767 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble 752023 1575 ns/op 1359 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble-2 699300 1557 ns/op 1359 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble-4 730688 1534 ns/op 1359 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble-8 768183 1508 ns/op 1359 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetRandomKeyInt/pebble-16 735848 1506 ns/op 1359 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetStruct
|
||||||
|
BenchmarkCacheGetStruct/encoding
|
||||||
|
BenchmarkCacheGetStruct/encoding 2252955 524 ns/op 208 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding-2 2332430 515 ns/op 208 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding-4 2251696 525 ns/op 208 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding-8 2235301 520 ns/op 208 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/encoding-16 2224682 527 ns/op 208 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map
|
||||||
|
BenchmarkCacheGetStruct/map 8009500 141 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-2 8406175 143 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-4 8249924 145 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-8 8324671 145 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/map-16 8102042 145 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/shard
|
||||||
|
BenchmarkCacheGetStruct/shard 7179788 164 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/shard-2 7332114 164 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/shard-4 6999268 174 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/shard-8 7028054 170 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/shard-16 6986014 170 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru
|
||||||
|
BenchmarkCacheGetStruct/lru 5818656 207 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-2 5859214 204 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-4 5518066 210 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-8 5618907 209 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/lru-16 5617592 214 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto
|
||||||
|
BenchmarkCacheGetStruct/ristretto 7409641 158 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-2 6809439 175 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-4 6004058 194 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-8 6170220 192 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/ristretto-16 6219170 190 ns/op 7 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache
|
||||||
|
BenchmarkCacheGetStruct/memcache 1412 801366 ns/op 424 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache-2 1395 845730 ns/op 424 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache-4 1454 754811 ns/op 424 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache-8 1509 754192 ns/op 425 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/memcache-16 1354 800273 ns/op 428 B/op 14 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis
|
||||||
|
BenchmarkCacheGetStruct/redis 553 2131603 ns/op 9720 B/op 32 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis-2 548 2139096 ns/op 9720 B/op 32 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis-4 537 2211997 ns/op 9720 B/op 32 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis-8 524 2189316 ns/op 9720 B/op 32 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/redis-16 548 2185637 ns/op 9720 B/op 32 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/pebble
|
||||||
|
BenchmarkCacheGetStruct/pebble 1427671 796 ns/op 1248 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/pebble-2 1448547 830 ns/op 1248 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/pebble-4 1405844 835 ns/op 1248 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/pebble-8 1441484 831 ns/op 1248 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheGetStruct/pebble-16 1387006 827 ns/op 1248 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetStruct
|
||||||
|
BenchmarkCacheSetStruct/encoding
|
||||||
|
BenchmarkCacheSetStruct/encoding 1000000 1625 ns/op 651 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/encoding-2 1720123 945 ns/op 457 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/encoding-4 1669809 705 ns/op 183 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/encoding-8 1657442 706 ns/op 183 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/encoding-16 1648228 709 ns/op 184 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/map
|
||||||
|
BenchmarkCacheSetStruct/map 1000000 1280 ns/op 410 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/map-2 1878842 1517 ns/op 341 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/map-4 1790534 692 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/map-8 1792663 665 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/map-16 1762833 677 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/shard
|
||||||
|
BenchmarkCacheSetStruct/shard 1000000 1437 ns/op 411 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/shard-2 1716608 830 ns/op 346 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/shard-4 1647408 736 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/shard-8 1657657 710 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/shard-16 1651122 711 ns/op 263 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/lru
|
||||||
|
BenchmarkCacheSetStruct/lru 1669929 717 ns/op 330 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/lru-2 1666970 686 ns/op 330 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/lru-4 1569268 707 ns/op 330 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/lru-8 1569517 701 ns/op 330 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/lru-16 1569993 720 ns/op 330 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/ristretto
|
||||||
|
BenchmarkCacheSetStruct/ristretto 1665415 1203 ns/op 406 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/ristretto-2 1000000 1111 ns/op 325 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/ristretto-4 1000000 1204 ns/op 319 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/ristretto-8 1000000 1193 ns/op 319 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/ristretto-16 946750 1171 ns/op 324 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/memcache
|
||||||
|
BenchmarkCacheSetStruct/memcache 1572 733672 ns/op 286 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/memcache-2 1341 799704 ns/op 286 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/memcache-4 1492 810459 ns/op 287 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/memcache-8 1500 807919 ns/op 289 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/memcache-16 1598 773923 ns/op 290 B/op 9 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/redis
|
||||||
|
BenchmarkCacheSetStruct/redis 848 1312946 ns/op 9788 B/op 35 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/redis-2 834 1370112 ns/op 9789 B/op 35 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/redis-4 858 1367748 ns/op 9789 B/op 35 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/redis-8 906 1348890 ns/op 9790 B/op 35 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/redis-16 856 1377737 ns/op 9791 B/op 35 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/pebble
|
||||||
|
BenchmarkCacheSetStruct/pebble 172 6891869 ns/op 179 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/pebble-2 176 7100201 ns/op 189 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/pebble-4 176 6765299 ns/op 417 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/pebble-8 174 6709812 ns/op 196 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheSetStruct/pebble-16 176 6872531 ns/op 207 B/op 4 allocs/op
|
||||||
|
BenchmarkCacheGetParallel
|
||||||
|
BenchmarkCacheGetParallel/encoding
|
||||||
|
BenchmarkCacheGetParallel/encoding 3755816 393 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/encoding-2 6620756 200 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/encoding-4 10706964 126 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/encoding-8 15889144 83.4 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/encoding-16 18838454 67.2 ns/op 192 B/op 2 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/map
|
||||||
|
BenchmarkCacheGetParallel/map 8287477 137 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/map-2 11197053 101 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/map-4 19310756 58.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/map-8 28979271 37.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/map-16 40122621 25.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/shard
|
||||||
|
BenchmarkCacheGetParallel/shard 6388084 175 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/shard-2 9824578 119 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/shard-4 16162353 70.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/shard-8 23337940 45.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/shard-16 34489749 31.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/lru
|
||||||
|
BenchmarkCacheGetParallel/lru 5497556 219 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/lru-2 5108966 239 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/lru-4 4236541 277 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/lru-8 3867518 313 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/lru-16 3719572 323 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/ristretto
|
||||||
|
BenchmarkCacheGetParallel/ristretto 6272048 170 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/ristretto-2 10652374 103 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/ristretto-4 15653863 73.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/ristretto-8 17346794 64.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/ristretto-16 18895278 57.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/memcache
|
||||||
|
BenchmarkCacheGetParallel/memcache 1398 868052 ns/op 432 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/memcache-2 2768 446176 ns/op 432 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/memcache-4 4094 244463 ns/op 439 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/memcache-8 8526 141027 ns/op 642 B/op 12 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/memcache-16 10852 108739 ns/op 871 B/op 13 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/redis
|
||||||
|
BenchmarkCacheGetParallel/redis 524 2255655 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/redis-2 933 1244186 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/redis-4 1560 790918 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/redis-8 1885 613956 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/redis-16 2102 558509 ns/op 9704 B/op 30 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/pebble
|
||||||
|
BenchmarkCacheGetParallel/pebble 1377561 970 ns/op 1248 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/pebble-2 2307592 630 ns/op 1248 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/pebble-4 3651379 352 ns/op 1248 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/pebble-8 5771799 222 ns/op 1248 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheGetParallel/pebble-16 7370930 187 ns/op 1248 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel
|
||||||
|
BenchmarkCacheSetParallel/encoding
|
||||||
|
BenchmarkCacheSetParallel/encoding 2877936 417 ns/op 176 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/encoding-2 3406563 377 ns/op 176 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/encoding-4 3595508 334 ns/op 176 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/encoding-8 3169240 378 ns/op 176 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/encoding-16 3012076 400 ns/op 176 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/map
|
||||||
|
BenchmarkCacheSetParallel/map 2573331 523 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/map-2 3006007 453 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/map-4 3168489 392 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/map-8 2839058 440 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/map-16 2834168 431 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/shard
|
||||||
|
BenchmarkCacheSetParallel/shard 2250218 516 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/shard-2 3581533 370 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/shard-4 3066703 415 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/shard-8 2774422 428 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/shard-16 2749574 432 ns/op 256 B/op 7 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/lru
|
||||||
|
BenchmarkCacheSetParallel/lru 3272673 430 ns/op 240 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/lru-2 4692276 278 ns/op 240 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/lru-4 3994620 312 ns/op 240 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/lru-8 3531354 341 ns/op 240 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/lru-16 3414451 353 ns/op 240 B/op 6 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/ristretto
|
||||||
|
BenchmarkCacheSetParallel/ristretto 2669528 456 ns/op 224 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/ristretto-2 2214732 547 ns/op 224 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/ristretto-4 2122172 564 ns/op 224 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/ristretto-8 1858959 639 ns/op 224 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/ristretto-16 1821427 656 ns/op 224 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/memcache
|
||||||
|
BenchmarkCacheSetParallel/memcache 1395469 863 ns/op 352 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/memcache-2 1672177 705 ns/op 352 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/memcache-4 848569 1414 ns/op 406 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/memcache-8 742070 1361 ns/op 402 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/memcache-16 1346508 950 ns/op 353 B/op 8 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/redis
|
||||||
|
BenchmarkCacheSetParallel/redis 100 21280373 ns/op 1526 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/redis-2 908 1203995 ns/op 1520 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/redis-4 901 1171409 ns/op 1520 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/redis-8 948 1185400 ns/op 1521 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/redis-16 852 1247485 ns/op 1523 B/op 31 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/pebble
|
||||||
|
BenchmarkCacheSetParallel/pebble 174 6865631 ns/op 178 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/pebble-2 189 6658668 ns/op 397 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/pebble-4 360 3477886 ns/op 184 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/pebble-8 717 1716858 ns/op 184 B/op 5 allocs/op
|
||||||
|
BenchmarkCacheSetParallel/pebble-16 1417 956456 ns/op 182 B/op 5 allocs/op
|
||||||
|
```
|
||||||
67
bench_test.go
Normal file
67
bench_test.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package cache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
glru "github.com/hashicorp/golang-lru"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/lru"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memcache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/pebble"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/redis"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/ristretto"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
"gitoa.ru/go-4devs/encoding/gob"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cacheBench struct {
|
||||||
|
name string
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheBenchList() []cacheBench {
|
||||||
|
client, _ := glru.New(10000)
|
||||||
|
db, cl := test.PebbleDB()
|
||||||
|
|
||||||
|
defer cl()
|
||||||
|
|
||||||
|
return []cacheBench{
|
||||||
|
{"encoding json", cache.New(memory.NewEncoding())},
|
||||||
|
{"encoding gob", cache.New(memory.NewEncoding(), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))},
|
||||||
|
{"map", cache.New(memory.NewMap())},
|
||||||
|
{"map shards", cache.New(memory.NewMapShard())},
|
||||||
|
{"ristretto", cache.New(ristretto.New(test.RistrettoClient()))},
|
||||||
|
{"lru", cache.New(lru.New(client))},
|
||||||
|
{"redis json", cache.New(redis.New(test.RedisClient()))},
|
||||||
|
{"redis gob", cache.New(redis.New(test.RedisClient()), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))},
|
||||||
|
{"memcache json", cache.New(memcache.New(test.MemcacheClient()))},
|
||||||
|
{"memcache gob", cache.New(memcache.New(test.MemcacheClient()), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))},
|
||||||
|
{"pebble json", cache.New(pebble.New(db))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
Key string
|
||||||
|
Val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheGetStruct(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var val testStruct
|
||||||
|
|
||||||
|
for _, c := range cacheBenchList() {
|
||||||
|
current := c.cache
|
||||||
|
require.Nil(b, current.Set(ctx, "key", testStruct{"key", c.name}, cache.WithTTL(time.Minute)))
|
||||||
|
|
||||||
|
b.Run(c.name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = current.Get(ctx, "key", &val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
121
cache.go
Normal file
121
cache.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure configure cache.
|
||||||
|
type Configure func(*Cache)
|
||||||
|
|
||||||
|
// WithDataOption sets cache default data options.
|
||||||
|
func WithDataOption(do ...Option) Configure {
|
||||||
|
return func(c *Cache) {
|
||||||
|
factory := c.dataFactory
|
||||||
|
c.dataFactory = func(key, value interface{}, opts ...Option) *Item {
|
||||||
|
return factory(key, value, append(do, opts...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultNamespace sets cache default namespace.
|
||||||
|
func WithDefaultNamespace(ns, separator string) Configure {
|
||||||
|
return WithDataOption(WithNamespace(ns, separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultTTL sets cache default ttl.
|
||||||
|
func WithDefaultTTL(ttl time.Duration) Configure {
|
||||||
|
return WithDataOption(WithTTL(ttl))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHandleSet add a handler for the set operation.
|
||||||
|
func WithHandleSet(m ...Handle) Configure {
|
||||||
|
return WithHandleOperation(OperationSet, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHandleGet add a handler for the get operation.
|
||||||
|
func WithHandleGet(m ...Handle) Configure {
|
||||||
|
return WithHandleOperation(OperationGet, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHandleDelete add a handler for the delete operation.
|
||||||
|
func WithHandleDelete(m ...Handle) Configure {
|
||||||
|
return WithHandleOperation(OperationDelete, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHandleOperation add a handler for the operation.
|
||||||
|
func WithHandleOperation(op string, m ...Handle) Configure {
|
||||||
|
handle := ChainHandle(m...)
|
||||||
|
|
||||||
|
return WithMiddleware(func(ctx context.Context, operation string, item *Item, next Provider) error {
|
||||||
|
if operation == op {
|
||||||
|
return handle(ctx, op, item, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, operation, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMiddleware sets middleware to provider.
|
||||||
|
func WithMiddleware(mw ...Handle) Configure {
|
||||||
|
return func(c *Cache) {
|
||||||
|
prov := c.provider
|
||||||
|
c.provider = chain(prov, mw...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates new cache by provider.
|
||||||
|
func New(prov Provider, opts ...Configure) *Cache {
|
||||||
|
c := &Cache{
|
||||||
|
provider: prov,
|
||||||
|
dataFactory: NewItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache base cache.
|
||||||
|
type Cache struct {
|
||||||
|
dataFactory func(key, value interface{}, opts ...Option) *Item
|
||||||
|
provider Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) With(opts ...Configure) *Cache {
|
||||||
|
cache := &Cache{
|
||||||
|
provider: c.provider,
|
||||||
|
dataFactory: c.dataFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Item(key, value interface{}, opts ...Option) *Item {
|
||||||
|
return c.dataFactory(key, value, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Execute(ctx context.Context, operation string, key, value interface{}, opts ...Option) error {
|
||||||
|
return c.provider(ctx, operation, c.Item(key, value, opts...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set handles middlewares and sets value by key and options.
|
||||||
|
func (c *Cache) Set(ctx context.Context, key, value interface{}, opts ...Option) error {
|
||||||
|
return c.Execute(ctx, OperationSet, key, value, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get handles middlewares and gets value by key and options.
|
||||||
|
func (c *Cache) Get(ctx context.Context, key, value interface{}, opts ...Option) error {
|
||||||
|
return c.Execute(ctx, OperationGet, key, value, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete handles middlewares and delete value by key and options.
|
||||||
|
func (c *Cache) Delete(ctx context.Context, key interface{}, opts ...Option) error {
|
||||||
|
return c.Execute(ctx, OperationDelete, key, nil, opts...)
|
||||||
|
}
|
||||||
226
cache_example_test.go
Normal file
226
cache_example_test.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package cache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
glru "github.com/hashicorp/golang-lru"
|
||||||
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/mw"
|
||||||
|
"gitoa.ru/go-4devs/cache/mw/prometheus"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/lru"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memcache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/redis"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
"gitoa.ru/go-4devs/encoding/gob"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCache_map() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(memory.NewMap())
|
||||||
|
|
||||||
|
var cached string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some value"))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
// Output:
|
||||||
|
// err: cache miss: map, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some value'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_encoding() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(memory.NewEncoding(), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))
|
||||||
|
|
||||||
|
var cached string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some value"))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
// Output:
|
||||||
|
// err: cache miss: encoding, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some value'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_redis() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(redis.New(test.RedisClient()))
|
||||||
|
|
||||||
|
var cached string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found redis key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some redis value", cache.WithNamespace("redis", ":")))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "redis:key", &cached), cached)
|
||||||
|
// Output:
|
||||||
|
// err: cache miss: redis pool, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some redis value'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_memacache() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(memcache.New(test.MemcacheClient()), cache.WithDataOption(cache.WithNamespace("memcache", ":")))
|
||||||
|
|
||||||
|
var cached string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found memcached key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not:found:memcached:key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some mamcache value"))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
// Output:
|
||||||
|
// err: key is not valid: memcache, value: ''
|
||||||
|
// err: cache miss: memcache, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some mamcache value'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_lru() {
|
||||||
|
ctx := context.Background()
|
||||||
|
client, _ := glru.New(10)
|
||||||
|
|
||||||
|
c := cache.New(lru.New(client), cache.WithDataOption(cache.WithTTL(time.Hour)))
|
||||||
|
|
||||||
|
var cached string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found lru key", &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some lru value"))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
fmt.Printf("deleted err: %v\n", c.Delete(ctx, "key"))
|
||||||
|
// Output:
|
||||||
|
// err: cache miss: lru, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some lru value'
|
||||||
|
// deleted err: <nil>
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_withNamespace() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(provider(), cache.WithDataOption(
|
||||||
|
cache.WithNamespace("prefix", ":"),
|
||||||
|
cache.WithTTL(time.Hour),
|
||||||
|
))
|
||||||
|
|
||||||
|
var cached, cached2 string
|
||||||
|
|
||||||
|
fmt.Printf("prefix err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
fmt.Printf("prefix err: %v\n", c.Set(ctx, "key", "some value", cache.WithTTL(time.Minute)))
|
||||||
|
fmt.Printf("prefix2 err: %v\n", c.Set(ctx, "key", "some value2", cache.WithNamespace("prefix2", ":")))
|
||||||
|
fmt.Printf("prefix err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached)
|
||||||
|
fmt.Printf("prefix2 err: %v, value: '%v'\n", c.Get(ctx, "key", &cached2, cache.WithNamespace("prefix2", ":")), cached2)
|
||||||
|
// Output:
|
||||||
|
// prefix err: cache miss: map, value: ''
|
||||||
|
// prefix err: <nil>
|
||||||
|
// prefix2 err: <nil>
|
||||||
|
// prefix err: <nil>, value: 'some value'
|
||||||
|
// prefix2 err: <nil>, value: 'some value2'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_withFallback() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(provider(), mw.WithFallback(
|
||||||
|
func(ctx context.Context, key, value interface{}) error {
|
||||||
|
fmt.Printf("loaded key: %#v\n", key)
|
||||||
|
|
||||||
|
return cache.TypeAssert("some loaded data", value)
|
||||||
|
},
|
||||||
|
func(i *cache.Item, e error) bool {
|
||||||
|
return e != nil
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
var cached, cached2 string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached)
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2)
|
||||||
|
// Output:
|
||||||
|
// loaded key: 1
|
||||||
|
// err: <nil>, value: 'some loaded data'
|
||||||
|
// err: <nil>, value: 'some loaded data'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_clearByContext() {
|
||||||
|
type ctxKey int
|
||||||
|
|
||||||
|
var (
|
||||||
|
requestID ctxKey = 1
|
||||||
|
cached, cached2 string
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), requestID, "unique ctx key"))
|
||||||
|
ctx2 := context.WithValue(context.Background(), requestID, "unique ctx key2")
|
||||||
|
c := cache.New(provider(),
|
||||||
|
mw.WithClearByContext(requestID),
|
||||||
|
cache.WithDataOption(cache.WithNamespace("clear_by_ctx", "")),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, 1, "some ctx loaded data", cache.WithTTL(time.Hour)))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached)
|
||||||
|
cancel()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx2, 1, &cached2), cached2)
|
||||||
|
// Output:
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some ctx loaded data'
|
||||||
|
// err: cache miss: map, value: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_clearByTTL() {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := cache.New(provider(),
|
||||||
|
mw.WithClearByTTL(),
|
||||||
|
cache.WithDataOption(cache.WithNamespace("clear_by_ttl", "")),
|
||||||
|
)
|
||||||
|
|
||||||
|
var cached, cached2 string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, 1, "some ttl loaded data", cache.WithTTL(time.Microsecond*200)))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2)
|
||||||
|
// Output:
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'some ttl loaded data'
|
||||||
|
// err: cache miss: map, value: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCache_withMetrics() {
|
||||||
|
ctx := context.Background()
|
||||||
|
cacheLabel := "cache_label"
|
||||||
|
c := cache.New(provider(),
|
||||||
|
mw.WithMetrics(prometheus.Metrics{}, mw.LabelName(cacheLabel)),
|
||||||
|
cache.WithDataOption(cache.WithNamespace("metrics", ":")),
|
||||||
|
)
|
||||||
|
|
||||||
|
var cached, cached2 string
|
||||||
|
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached)
|
||||||
|
fmt.Printf("err: %v\n", c.Set(ctx, 1, "cached"))
|
||||||
|
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2)
|
||||||
|
|
||||||
|
mfs, _ := prom.DefaultGatherer.Gather()
|
||||||
|
for _, mf := range mfs {
|
||||||
|
for _, metric := range mf.GetMetric() {
|
||||||
|
label := metric.GetLabel()
|
||||||
|
if len(label) > 0 && metric.Counter != nil {
|
||||||
|
fmt.Printf("name:%s, label:%s, value: %.0f\n", *mf.Name, *label[0].Value, mf.GetMetric()[0].Counter.GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// err: cache miss: map, value: ''
|
||||||
|
// err: <nil>
|
||||||
|
// err: <nil>, value: 'cached'
|
||||||
|
// name:cache_hit_total, label:cache_label, value: 1
|
||||||
|
// name:cache_miss_total, label:cache_label, value: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func provider() cache.Provider {
|
||||||
|
return memory.NewMap()
|
||||||
|
}
|
||||||
208
cache_test.go
Normal file
208
cache_test.go
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package cache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache_Get(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pro := test.NewProviderMock(t, test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
u := test.NewUser(1)
|
||||||
|
|
||||||
|
return cache.TypeAssert(u, d.Value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
cache := cache.New(pro)
|
||||||
|
|
||||||
|
var user test.User
|
||||||
|
|
||||||
|
require.Nil(t, cache.Get(ctx, 1, &user))
|
||||||
|
require.Equal(t, test.NewUser(1), user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_Set(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pro := test.NewProviderMock(t, test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
require.Equal(t, 1, d.Key.Key)
|
||||||
|
require.Equal(t, test.NewUser(1), d.Value)
|
||||||
|
require.Equal(t, "1", d.Key.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
cache := cache.New(pro)
|
||||||
|
|
||||||
|
require.Nil(t, cache.Set(ctx, 1, test.NewUser(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_Delete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pro := test.NewProviderMock(t, test.WithDelete(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
require.Equal(t, 1, d.Key.Key)
|
||||||
|
require.Empty(t, d.Value)
|
||||||
|
require.Equal(t, "1", d.Key.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
cache := cache.New(pro)
|
||||||
|
|
||||||
|
require.Nil(t, cache.Delete(ctx, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_Get_withMiddleware(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pro := test.NewProviderMock(t,
|
||||||
|
test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
require.Equal(t, 2, d.Key.Key)
|
||||||
|
require.Equal(t, test.NewUser(2), d.Value)
|
||||||
|
require.Equal(t, "mw_prefix::_2", d.Key.String())
|
||||||
|
require.Equal(t, time.Minute, d.TTL)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
require.Equal(t, 2, d.Key.Key)
|
||||||
|
require.Equal(t, "mw_prefix----2", d.Key.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
c := cache.New(pro,
|
||||||
|
cache.WithDataOption(
|
||||||
|
func(i *cache.Item) {
|
||||||
|
i.Key.Prefix = "mw_prefix"
|
||||||
|
},
|
||||||
|
cache.WithTTL(time.Hour),
|
||||||
|
),
|
||||||
|
cache.WithHandleSet(
|
||||||
|
func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error {
|
||||||
|
d.Key.Separator = "::"
|
||||||
|
|
||||||
|
return n(ctx, op, d)
|
||||||
|
}, func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error {
|
||||||
|
d.Key.Separator += "_"
|
||||||
|
|
||||||
|
return n(ctx, op, d)
|
||||||
|
}),
|
||||||
|
cache.WithHandleGet(func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error {
|
||||||
|
d.Key.Separator = "----"
|
||||||
|
|
||||||
|
return n(ctx, op, d)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
var user test.User
|
||||||
|
|
||||||
|
require.Nil(t, c.Set(ctx, 2, test.NewUser(2), cache.WithTTL(time.Minute)))
|
||||||
|
require.Nil(t, c.Get(ctx, 2, &user))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheWith(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pro := test.NewProviderMock(t,
|
||||||
|
test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
switch d.Key.Key.(int) {
|
||||||
|
case 1:
|
||||||
|
require.Equal(t, "ns:1", d.Key.String())
|
||||||
|
case 2:
|
||||||
|
require.Equal(t, "new_ns_2", d.Key.String())
|
||||||
|
default:
|
||||||
|
t.Errorf("key %v no allowed", d.Key.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, d *cache.Item) error {
|
||||||
|
switch d.Key.Key.(int) {
|
||||||
|
case 1:
|
||||||
|
require.Equal(t, time.Hour, d.TTL)
|
||||||
|
case 2:
|
||||||
|
require.Equal(t, time.Minute, d.TTL)
|
||||||
|
default:
|
||||||
|
t.Errorf("key %v no allowed", d.Key.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cntSetCache2, cntGetCache1 int32
|
||||||
|
val1, val2 string
|
||||||
|
)
|
||||||
|
|
||||||
|
cache1 := cache.New(pro,
|
||||||
|
cache.WithHandleGet(func(ctx context.Context, operation string, item *cache.Item, next cache.Provider) error {
|
||||||
|
atomic.AddInt32(&cntGetCache1, 1)
|
||||||
|
|
||||||
|
return next(ctx, operation, item)
|
||||||
|
}),
|
||||||
|
cache.WithDataOption(
|
||||||
|
cache.WithNamespace("ns", ":"),
|
||||||
|
cache.WithTTL(time.Hour),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cache2 := cache1.With(
|
||||||
|
cache.WithHandleSet(func(ctx context.Context, operation string, item *cache.Item, next cache.Provider) error {
|
||||||
|
atomic.AddInt32(&cntSetCache2, 1)
|
||||||
|
|
||||||
|
return next(ctx, operation, item)
|
||||||
|
}),
|
||||||
|
cache.WithDataOption(
|
||||||
|
cache.WithNamespace("new_ns", "_"),
|
||||||
|
cache.WithTTL(time.Minute),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, cache1.Get(ctx, 1, &val1))
|
||||||
|
require.NoError(t, cache2.Get(ctx, 2, &val2))
|
||||||
|
|
||||||
|
require.NoError(t, cache1.Set(ctx, 1, val1))
|
||||||
|
require.NoError(t, cache2.Set(ctx, 2, val2))
|
||||||
|
|
||||||
|
require.Equal(t, int32(1), cntSetCache2)
|
||||||
|
require.Equal(t, int32(2), cntGetCache1)
|
||||||
|
}
|
||||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
container_name: 4devs_cache_redis
|
||||||
|
image: redis:latest
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:6379:6379"
|
||||||
|
memcached:
|
||||||
|
container_name: 4devs_cache_memcached
|
||||||
|
image: memcached:latest
|
||||||
|
ports:
|
||||||
|
- "11211:11211"
|
||||||
33
error.go
Normal file
33
error.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cached errors.
|
||||||
|
var (
|
||||||
|
ErrCacheMiss = errors.New("cache miss")
|
||||||
|
ErrCacheExpired = errors.New("cache expired")
|
||||||
|
ErrSourceNotValid = errors.New("source is not valid")
|
||||||
|
ErrKeyNotValid = errors.New("key is not valid")
|
||||||
|
ErrTargetNil = errors.New("target is nil")
|
||||||
|
ErrOperationNotAllwed = errors.New("operation not allowed")
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ error = NewErrorTarget(nil)
|
||||||
|
|
||||||
|
// NewErrorTarget creates new target error.
|
||||||
|
func NewErrorTarget(target interface{}) ErrorTarget {
|
||||||
|
return ErrorTarget{target: target}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorTarget errs target is not a settable.
|
||||||
|
type ErrorTarget struct {
|
||||||
|
target interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorTarget errors.
|
||||||
|
func (e ErrorTarget) Error() string {
|
||||||
|
return fmt.Sprintf("target is not a settable %T", e.target)
|
||||||
|
}
|
||||||
15
go.mod
Normal file
15
go.mod
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
module gitoa.ru/go-4devs/cache
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
|
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391
|
||||||
|
github.com/dgraph-io/ristretto v0.0.3
|
||||||
|
github.com/gomodule/redigo v1.8.2
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
|
github.com/mailru/easyjson v0.7.6
|
||||||
|
github.com/prometheus/client_golang v1.7.1
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.3
|
||||||
|
)
|
||||||
185
go.sum
Normal file
185
go.sum
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs=
|
||||||
|
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
|
||||||
|
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=
|
||||||
|
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
|
||||||
|
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391 h1:iCTxMsf8E/rUVDw+tqgOHnYBJiXg4dU9psCv4jjI9n0=
|
||||||
|
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391/go.mod h1:hU7vhtrqonEphNF+xt8/lHdaBprxmV1h8BOGrd9XwmQ=
|
||||||
|
github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3 h1:2+dpIJzYMSbLi0587YXpi8tOJT52qCOI/1I0UNThc/I=
|
||||||
|
github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||||
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
|
||||||
|
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
|
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||||
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
|
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||||
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws=
|
||||||
|
github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
|
||||||
|
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.3 h1:Rqjs0lsnco5PfMZ4iP3+TFYd/dHbG8FqMXxmbgTdfq4=
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.3/go.mod h1:TPHAyATVNvRFt4Z+4beRhg9+E/VpXxwpniZfzkH9258=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc=
|
||||||
|
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
147
item.go
Normal file
147
item.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ fmt.Stringer = (*Key)(nil)
|
||||||
|
|
||||||
|
// Option ffor the configuration item.
|
||||||
|
type Option func(*Item)
|
||||||
|
|
||||||
|
// WithNamespace sets prefix and separator.
|
||||||
|
func WithNamespace(prefix, sep string) Option {
|
||||||
|
return func(d *Item) {
|
||||||
|
d.Key.Prefix = prefix
|
||||||
|
d.Key.Separator = sep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTTL sets ttl.
|
||||||
|
func WithTTL(ttl time.Duration) Option {
|
||||||
|
return func(d *Item) {
|
||||||
|
d.TTL = ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMarshal sets marshal and unmarshal.
|
||||||
|
func WithMarshal(unmarshal encoding.Unmarshal, marshal encoding.Marshal) Option {
|
||||||
|
return func(d *Item) {
|
||||||
|
d.unmarshal = unmarshal
|
||||||
|
d.marshal = marshal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewItem creates and configure new item.
|
||||||
|
func NewItem(key, value interface{}, opts ...Option) *Item {
|
||||||
|
item := &Item{
|
||||||
|
Key: Key{
|
||||||
|
Key: key,
|
||||||
|
Prefix: "",
|
||||||
|
Separator: "",
|
||||||
|
},
|
||||||
|
Value: value,
|
||||||
|
TTL: 0,
|
||||||
|
unmarshal: json.Unmarshal,
|
||||||
|
marshal: json.Marshal,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item to pass to the provider.
|
||||||
|
type Item struct {
|
||||||
|
Key Key
|
||||||
|
Value interface{}
|
||||||
|
TTL time.Duration
|
||||||
|
unmarshal encoding.Unmarshal
|
||||||
|
marshal encoding.Marshal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Item) With(key, val interface{}, opts ...Option) *Item {
|
||||||
|
return NewItem(key, val, append(i.Options(), opts...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired checks expired item.
|
||||||
|
func (i *Item) IsExpired() bool {
|
||||||
|
return i.TTL < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Item) Marshal() ([]byte, error) {
|
||||||
|
return i.marshal(i.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Item) Unmarshal(data []byte) error {
|
||||||
|
return i.unmarshal(data, i.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options gets item options.
|
||||||
|
func (i *Item) Options() []Option {
|
||||||
|
opts := []Option{WithTTL(i.TTL), WithMarshal(i.unmarshal, i.marshal)}
|
||||||
|
|
||||||
|
if i.Key.Prefix != "" {
|
||||||
|
opts = append(opts, WithNamespace(i.Key.Prefix, i.Key.Separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTLInSecond to set the ttl in seconds.
|
||||||
|
func (i *Item) TTLInSecond(in int64) {
|
||||||
|
i.TTL = time.Second * time.Duration(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired get the time when the ttl is outdated.
|
||||||
|
func (i *Item) Expired() time.Time {
|
||||||
|
return time.Now().Add(i.TTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key with prefix and separator.
|
||||||
|
type Key struct {
|
||||||
|
Key interface{}
|
||||||
|
Prefix string
|
||||||
|
Separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) Value() interface{} {
|
||||||
|
if v, ok := k.Key.(interface{ Value() interface{} }); ok {
|
||||||
|
return v.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a formatted key.
|
||||||
|
func (k Key) String() string {
|
||||||
|
if k.Prefix != "" {
|
||||||
|
return fmt.Sprint(k.Prefix, k.Separator, k.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := k.Key.(type) {
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(v)
|
||||||
|
case int32:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(v, 10)
|
||||||
|
case uint:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint32:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint64:
|
||||||
|
return strconv.FormatUint(v, 10)
|
||||||
|
case string:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
56
item/expired.go
Normal file
56
item/expired.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package item
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate easyjson
|
||||||
|
|
||||||
|
//easyjson:json
|
||||||
|
type expiredByte struct {
|
||||||
|
Data []byte `json:"d"`
|
||||||
|
Expired time.Time `json:"e"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalExpired(item *cache.Item) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
e expiredByte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
e.Data, err = item.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed marshal expired: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.TTL > 0 {
|
||||||
|
e.Expired = item.Expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalExpired(item *cache.Item, d []byte) error {
|
||||||
|
var e expiredByte
|
||||||
|
|
||||||
|
if err := e.UnmarshalJSON(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.Expired.IsZero() {
|
||||||
|
item.TTL = time.Until(e.Expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.IsExpired() {
|
||||||
|
return cache.ErrCacheExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := item.Unmarshal(e.Data); err != nil {
|
||||||
|
return fmt.Errorf("failed unmarshal expired: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
99
item/expired_easyjson.go
Normal file
99
item/expired_easyjson.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
||||||
|
|
||||||
|
package item
|
||||||
|
|
||||||
|
import (
|
||||||
|
json "encoding/json"
|
||||||
|
easyjson "github.com/mailru/easyjson"
|
||||||
|
jlexer "github.com/mailru/easyjson/jlexer"
|
||||||
|
jwriter "github.com/mailru/easyjson/jwriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// suppress unused package warning
|
||||||
|
var (
|
||||||
|
_ *json.RawMessage
|
||||||
|
_ *jlexer.Lexer
|
||||||
|
_ *jwriter.Writer
|
||||||
|
_ easyjson.Marshaler
|
||||||
|
)
|
||||||
|
|
||||||
|
func easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(in *jlexer.Lexer, out *expiredByte) {
|
||||||
|
isTopLevel := in.IsStart()
|
||||||
|
if in.IsNull() {
|
||||||
|
if isTopLevel {
|
||||||
|
in.Consumed()
|
||||||
|
}
|
||||||
|
in.Skip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
in.Delim('{')
|
||||||
|
for !in.IsDelim('}') {
|
||||||
|
key := in.UnsafeFieldName(false)
|
||||||
|
in.WantColon()
|
||||||
|
if in.IsNull() {
|
||||||
|
in.Skip()
|
||||||
|
in.WantComma()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch key {
|
||||||
|
case "d":
|
||||||
|
if in.IsNull() {
|
||||||
|
in.Skip()
|
||||||
|
out.Data = nil
|
||||||
|
} else {
|
||||||
|
out.Data = in.Bytes()
|
||||||
|
}
|
||||||
|
case "e":
|
||||||
|
if data := in.Raw(); in.Ok() {
|
||||||
|
in.AddError((out.Expired).UnmarshalJSON(data))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
in.SkipRecursive()
|
||||||
|
}
|
||||||
|
in.WantComma()
|
||||||
|
}
|
||||||
|
in.Delim('}')
|
||||||
|
if isTopLevel {
|
||||||
|
in.Consumed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(out *jwriter.Writer, in expiredByte) {
|
||||||
|
out.RawByte('{')
|
||||||
|
first := true
|
||||||
|
_ = first
|
||||||
|
{
|
||||||
|
const prefix string = ",\"d\":"
|
||||||
|
out.RawString(prefix[1:])
|
||||||
|
out.Base64Bytes(in.Data)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const prefix string = ",\"e\":"
|
||||||
|
out.RawString(prefix)
|
||||||
|
out.Raw((in.Expired).MarshalJSON())
|
||||||
|
}
|
||||||
|
out.RawByte('}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON supports json.Marshaler interface
|
||||||
|
func (v expiredByte) MarshalJSON() ([]byte, error) {
|
||||||
|
w := jwriter.Writer{}
|
||||||
|
easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(&w, v)
|
||||||
|
return w.Buffer.BuildBytes(), w.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||||
|
func (v expiredByte) MarshalEasyJSON(w *jwriter.Writer) {
|
||||||
|
easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(w, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON supports json.Unmarshaler interface
|
||||||
|
func (v *expiredByte) UnmarshalJSON(data []byte) error {
|
||||||
|
r := jlexer.Lexer{Data: data}
|
||||||
|
easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(&r, v)
|
||||||
|
return r.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||||
|
func (v *expiredByte) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||||
|
easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(l, v)
|
||||||
|
}
|
||||||
134
mw/fallback.go
Normal file
134
mw/fallback.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package mw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fallback func(ctx context.Context, key, value interface{}) error
|
||||||
|
|
||||||
|
type Getter func(ctx context.Context, key interface{}) (interface{}, error)
|
||||||
|
|
||||||
|
// HandleByErr checks if cache return err.
|
||||||
|
func HandleByErr(_ *cache.Item, err error) bool {
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockFallback locks run fallback by item key.
|
||||||
|
func LockFallback(fallback Fallback) Fallback {
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
item interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make(map[interface{}]chan entry)
|
||||||
|
|
||||||
|
return func(ctx context.Context, key, value interface{}) error {
|
||||||
|
mu.Lock()
|
||||||
|
if _, ok := keys[key]; !ok {
|
||||||
|
keys[key] = make(chan entry, 1)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
err := fallback(ctx, key, value)
|
||||||
|
keys[key] <- entry{
|
||||||
|
item: value,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(keys[key])
|
||||||
|
delete(keys, key)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
entry := <-keys[key]
|
||||||
|
if entry.err != nil {
|
||||||
|
return entry.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.TypeAssert(entry.item, value); err != nil {
|
||||||
|
return fmt.Errorf("%w: assert value", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFallback sets fallback when cache handle success and set result in cache.
|
||||||
|
func WithFallback(fallback Fallback, isHandleFallback func(*cache.Item, error) bool) cache.Configure {
|
||||||
|
return cache.WithHandleGet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
err := next(ctx, op, item)
|
||||||
|
if isHandleFallback(item, err) {
|
||||||
|
if ferr := fallback(ctx, item.Key.Key, item.Value); ferr != nil {
|
||||||
|
return ferr
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, cache.OperationSet, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLockGetter sets values from getter when cache handle success and set result in cache.
|
||||||
|
func WithLockGetter(getter Getter, isHandle func(*cache.Item, error) bool) cache.Configure {
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
value interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make(map[cache.Key]chan entry)
|
||||||
|
|
||||||
|
return cache.WithHandleGet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
if err := next(ctx, op, item); !isHandle(item, err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
if _, ok := keys[item.Key]; !ok {
|
||||||
|
keys[item.Key] = make(chan entry, 1)
|
||||||
|
mu.Unlock()
|
||||||
|
value, gerr := getter(ctx, item.Key.Value())
|
||||||
|
keys[item.Key] <- entry{
|
||||||
|
value: value,
|
||||||
|
err: gerr,
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(keys[item.Key])
|
||||||
|
delete(keys, item.Key)
|
||||||
|
}()
|
||||||
|
if gerr != nil {
|
||||||
|
return gerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.TypeAssert(value, item.Value); err != nil {
|
||||||
|
return fmt.Errorf("lock failed assert type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
entry := <-keys[item.Key]
|
||||||
|
if entry.err != nil {
|
||||||
|
return entry.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.TypeAssert(entry.value, item.Value); err != nil {
|
||||||
|
return fmt.Errorf("lock failed assert type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, cache.OperationSet, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
221
mw/fallback_test.go
Normal file
221
mw/fallback_test.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package mw_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/mw"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errFallback = errors.New("fallback error")
|
||||||
|
errKey = errors.New("unexpected key")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithFallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
key1 := "fallback:key1"
|
||||||
|
key2 := "fb:key2"
|
||||||
|
|
||||||
|
prov := test.NewProviderMock(t,
|
||||||
|
test.WithGet(cacheGetMiss),
|
||||||
|
test.WithSet(cacheSetMiss(map[interface{}]test.User{
|
||||||
|
key1: test.NewUser(1),
|
||||||
|
key2: test.NewUser(2),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
c := cache.New(prov, mw.WithFallback(
|
||||||
|
func(ctx context.Context, key, value interface{}) error {
|
||||||
|
switch key.(string) {
|
||||||
|
case key1:
|
||||||
|
*value.(*test.User) = test.NewUser(1)
|
||||||
|
case key2:
|
||||||
|
*value.(*test.User) = test.NewUser(2)
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
mw.HandleByErr,
|
||||||
|
))
|
||||||
|
|
||||||
|
var user test.User
|
||||||
|
|
||||||
|
require.Nil(t, c.Get(ctx, key1, &user))
|
||||||
|
require.Equal(t, test.NewUser(1), user)
|
||||||
|
|
||||||
|
require.Nil(t, c.Get(ctx, key2, &user, cache.WithNamespace("namespace", ":")))
|
||||||
|
require.Equal(t, test.NewUser(2), user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockFallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var (
|
||||||
|
val1, val2, val3 string
|
||||||
|
wg sync.WaitGroup
|
||||||
|
cnt int32
|
||||||
|
)
|
||||||
|
|
||||||
|
fallback := mw.LockFallback(func(ctx context.Context, key, value interface{}) error {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
*value.(*string) = fmt.Sprintf("value:%v", cnt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
assert.Nil(t, fallback(ctx, 1, &val1))
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
assert.Nil(t, fallback(ctx, 1, &val2))
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
require.Equal(t, "value:1", val1)
|
||||||
|
require.Equal(t, "value:1", val2)
|
||||||
|
|
||||||
|
assert.Nil(t, fallback(ctx, 1, &val3))
|
||||||
|
require.Equal(t, "value:2", val3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockFallback_Error(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var (
|
||||||
|
val1, val2, val3 string
|
||||||
|
wg sync.WaitGroup
|
||||||
|
cnt int32
|
||||||
|
)
|
||||||
|
|
||||||
|
fallback := mw.LockFallback(func(ctx context.Context, key, value interface{}) error {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
|
||||||
|
return fmt.Errorf("%w:%v", errFallback, cnt)
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
assert.EqualError(t, fallback(ctx, 1, &val1), "fallback error:1")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
assert.EqualError(t, fallback(ctx, 1, &val2), "fallback error:1")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
require.Empty(t, val1)
|
||||||
|
require.Empty(t, val2)
|
||||||
|
|
||||||
|
assert.EqualError(t, fallback(ctx, 1, val3), "fallback error:2")
|
||||||
|
require.Empty(t, val3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithLockGetter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
key1 := "getter:key1"
|
||||||
|
|
||||||
|
var cnt int32
|
||||||
|
|
||||||
|
prov := test.NewProviderMock(t,
|
||||||
|
test.WithGet(cacheGetMiss),
|
||||||
|
test.WithSet(cacheSetMiss(
|
||||||
|
map[interface{}]test.User{
|
||||||
|
key1: test.NewUser(1),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
c := cache.New(prov,
|
||||||
|
cache.WithDataOption(
|
||||||
|
cache.WithNamespace("gn", ":"),
|
||||||
|
),
|
||||||
|
mw.WithLockGetter(
|
||||||
|
func(ctx context.Context, key interface{}) (interface{}, error) {
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
time.Sleep(time.Second / 2)
|
||||||
|
switch key.(string) {
|
||||||
|
case key1:
|
||||||
|
return test.NewUser(1), nil
|
||||||
|
case "key2":
|
||||||
|
return test.NewUser(2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%w: key '%v'", errKey, key)
|
||||||
|
},
|
||||||
|
mw.HandleByErr,
|
||||||
|
))
|
||||||
|
|
||||||
|
var (
|
||||||
|
user1, user2 test.User
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
require.Nil(t, c.Get(ctx, key1, &user1))
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
require.Nil(t, c.Get(ctx, key1, &user2))
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
require.Equal(t, test.NewUser(1), user1)
|
||||||
|
require.Equal(t, test.NewUser(1), user2)
|
||||||
|
require.Equal(t, int32(1), cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheGetMiss(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, item *cache.Item) error {
|
||||||
|
return cache.ErrCacheMiss
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheSetMiss(items map[interface{}]test.User) func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
return func(t *testing.T) func(ctx context.Context, item *cache.Item) error {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(ctx context.Context, item *cache.Item) error {
|
||||||
|
if value, ok := items[item.Key.Key]; ok {
|
||||||
|
require.Equal(t, &value, item.Value)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("unexpected key %v", item.Key.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
mw/gc.go
Normal file
78
mw/gc.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package mw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key struct {
|
||||||
|
key interface{}
|
||||||
|
ctxPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k key) Value() interface{} {
|
||||||
|
return k.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k key) String() string {
|
||||||
|
return fmt.Sprint(k.ctxPrefix, k.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClearByContext clear cache if context done.
|
||||||
|
func WithClearByContext(ctxKey interface{}) cache.Configure {
|
||||||
|
operation := func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
ctxPrefix, ok := ctx.Value(ctxKey).(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: must be unique ctx key", cache.ErrKeyNotValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := item.Key.Key
|
||||||
|
item.Key.Key = key{
|
||||||
|
key: k,
|
||||||
|
ctxPrefix: ctxPrefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, op, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.WithMiddleware(
|
||||||
|
func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
if op == cache.OperationSet {
|
||||||
|
go func(ctx context.Context, item *cache.Item) {
|
||||||
|
<-ctx.Done()
|
||||||
|
_ = next(ctx, cache.OperationDelete, item)
|
||||||
|
}(ctx, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation(ctx, op, item, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClearByTTL clear cache by key after ttl.
|
||||||
|
func WithClearByTTL() cache.Configure {
|
||||||
|
keys := make(map[cache.Key]*time.Timer)
|
||||||
|
mu := sync.Mutex{}
|
||||||
|
|
||||||
|
return cache.WithHandleSet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
if item.TTL > 0 {
|
||||||
|
go func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if t, ok := keys[item.Key]; ok {
|
||||||
|
t.Reset(item.TTL)
|
||||||
|
} else {
|
||||||
|
keys[item.Key] = time.AfterFunc(item.TTL, func() {
|
||||||
|
_ = next(ctx, cache.OperationDelete, item)
|
||||||
|
delete(keys, item.Key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, op, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
68
mw/gc_test.go
Normal file
68
mw/gc_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package mw_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/mw"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithClearByTTL(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
gcMap := cache.New(memory.NewMap(), mw.WithClearByTTL())
|
||||||
|
cacheMap := cache.New(memory.NewMap())
|
||||||
|
|
||||||
|
var (
|
||||||
|
value string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/3)))
|
||||||
|
require.NoError(t, cacheMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/3)))
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
err = gcMap.Get(ctx, "keys", &value)
|
||||||
|
require.EqualError(t, err, cache.ErrCacheMiss.Error()+": map")
|
||||||
|
|
||||||
|
err = cacheMap.Get(ctx, "keys", &value)
|
||||||
|
require.EqualError(t, err, cache.ErrCacheExpired.Error()+": map")
|
||||||
|
|
||||||
|
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/2)))
|
||||||
|
time.AfterFunc(time.Second/3, func() {
|
||||||
|
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second)))
|
||||||
|
})
|
||||||
|
time.Sleep(time.Second / 2)
|
||||||
|
require.NoError(t, gcMap.Get(ctx, "keys", &value))
|
||||||
|
require.Equal(t, value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithClearByContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type ctxKey int
|
||||||
|
|
||||||
|
var (
|
||||||
|
requestID ctxKey = 1
|
||||||
|
data string
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx1, cancel1 := context.WithCancel(context.WithValue(context.Background(), requestID, "request1"))
|
||||||
|
ctx2, cancel2 := context.WithCancel(context.WithValue(context.Background(), requestID, "request2"))
|
||||||
|
|
||||||
|
cacheMap := cache.New(memory.NewMap(), mw.WithClearByContext(requestID))
|
||||||
|
|
||||||
|
require.NoError(t, cacheMap.Set(ctx1, "key", "value"))
|
||||||
|
require.EqualError(t, cacheMap.Get(ctx2, "key", &data), "cache miss: map")
|
||||||
|
require.NoError(t, cacheMap.Get(ctx1, "key", &data))
|
||||||
|
cancel1()
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
require.EqualError(t, cacheMap.Get(ctx1, "key", &data), "cache miss: map")
|
||||||
|
cancel2()
|
||||||
|
}
|
||||||
60
mw/metrics.go
Normal file
60
mw/metrics.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package mw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics interface for middleware.
|
||||||
|
type Metrics interface {
|
||||||
|
Hit(label string)
|
||||||
|
Miss(label string)
|
||||||
|
Expired(label string)
|
||||||
|
Err(label string, operation string)
|
||||||
|
Observe(label string, operation string, start time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelName sets static name.
|
||||||
|
func LabelName(name string) func(ctx context.Context, item *cache.Item) string {
|
||||||
|
return func(_ context.Context, _ *cache.Item) string {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelPreficKey gets lebale by item prefix.
|
||||||
|
func LabelPreficKey(ctx context.Context, item *cache.Item) string {
|
||||||
|
return item.Key.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMetrics cache middleware metrics.
|
||||||
|
func WithMetrics(m Metrics, labelCallback func(ctx context.Context, item *cache.Item) string) cache.Configure {
|
||||||
|
return cache.WithMiddleware(
|
||||||
|
func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error {
|
||||||
|
label := labelCallback(ctx, item)
|
||||||
|
start := time.Now()
|
||||||
|
err := next(ctx, op, item)
|
||||||
|
m.Observe(label, op, start)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, cache.ErrCacheMiss):
|
||||||
|
m.Miss(label)
|
||||||
|
case errors.Is(err, cache.ErrCacheExpired):
|
||||||
|
m.Expired(label)
|
||||||
|
default:
|
||||||
|
m.Err(label, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if op == cache.OperationGet {
|
||||||
|
m.Hit(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
93
mw/prometheus/metrics.go
Normal file
93
mw/prometheus/metrics.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metrics "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"gitoa.ru/go-4devs/cache/mw"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
labelSet = "label"
|
||||||
|
labelOperation = "operation"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint: gochecknoglobals
|
||||||
|
var (
|
||||||
|
hitCount = metrics.NewCounterVec(
|
||||||
|
metrics.CounterOpts{
|
||||||
|
Name: "cache_hit_total",
|
||||||
|
Help: "Counter of hits cache.",
|
||||||
|
},
|
||||||
|
[]string{labelSet},
|
||||||
|
)
|
||||||
|
missCount = metrics.NewCounterVec(
|
||||||
|
metrics.CounterOpts{
|
||||||
|
Name: "cache_miss_total",
|
||||||
|
Help: "Counter of misses cache.",
|
||||||
|
},
|
||||||
|
[]string{labelSet},
|
||||||
|
)
|
||||||
|
expiredCount = metrics.NewCounterVec(
|
||||||
|
metrics.CounterOpts{
|
||||||
|
Name: "cache_expired_total",
|
||||||
|
Help: "Counter of expired items in cache.",
|
||||||
|
},
|
||||||
|
[]string{labelSet},
|
||||||
|
)
|
||||||
|
errorsCount = metrics.NewCounterVec(
|
||||||
|
metrics.CounterOpts{
|
||||||
|
Name: "cache_errors_total",
|
||||||
|
Help: "Counter of errors in cache.",
|
||||||
|
},
|
||||||
|
[]string{labelSet, labelOperation},
|
||||||
|
)
|
||||||
|
responseTime = metrics.NewHistogramVec(
|
||||||
|
metrics.HistogramOpts{
|
||||||
|
Name: "cache_request_duration_seconds",
|
||||||
|
Help: "Histogram of RT for the request cache (seconds).",
|
||||||
|
},
|
||||||
|
[]string{labelSet, labelOperation},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint: gochecknoinits
|
||||||
|
func init() {
|
||||||
|
metrics.MustRegister(
|
||||||
|
hitCount,
|
||||||
|
missCount,
|
||||||
|
expiredCount,
|
||||||
|
errorsCount,
|
||||||
|
responseTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ mw.Metrics = Metrics{}
|
||||||
|
|
||||||
|
// Metrics prometeus.
|
||||||
|
type Metrics struct{}
|
||||||
|
|
||||||
|
// Miss inc miss error cache.
|
||||||
|
func (m Metrics) Miss(label string) {
|
||||||
|
missCount.WithLabelValues(label).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hit increment hit cache.
|
||||||
|
func (m Metrics) Hit(label string) {
|
||||||
|
hitCount.WithLabelValues(label).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired increment error expired.
|
||||||
|
func (m Metrics) Expired(label string) {
|
||||||
|
expiredCount.WithLabelValues(label).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err increment base undefined error.
|
||||||
|
func (m Metrics) Err(label string, operation string) {
|
||||||
|
errorsCount.WithLabelValues(label, operation).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe time from start.
|
||||||
|
func (m Metrics) Observe(label string, operation string, start time.Time) {
|
||||||
|
responseTime.WithLabelValues(label, operation).Observe(float64(time.Since(start)) / float64(time.Second))
|
||||||
|
}
|
||||||
21
operation.go
Normal file
21
operation.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// available operation.
|
||||||
|
const (
|
||||||
|
OperationGet = "get"
|
||||||
|
OperationSet = "set"
|
||||||
|
OperationDelete = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OperationProvider creating a provider based on available operations.
|
||||||
|
func OperationProvider(prov map[string]func(ctx context.Context, item *Item) error) Provider {
|
||||||
|
return func(ctx context.Context, operation string, item *Item) error {
|
||||||
|
if method, ok := prov[operation]; ok {
|
||||||
|
return method(ctx, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrOperationNotAllwed
|
||||||
|
}
|
||||||
|
}
|
||||||
50
provider.go
Normal file
50
provider.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider for different types of cache, in memory, lru, redis.
|
||||||
|
type Provider func(ctx context.Context, operation string, item *Item) error
|
||||||
|
|
||||||
|
// Handle middleware before/after provider.
|
||||||
|
type Handle func(ctx context.Context, operation string, item *Item, next Provider) error
|
||||||
|
|
||||||
|
// ChainHandle chain handle middleware.
|
||||||
|
func ChainHandle(handle ...Handle) Handle {
|
||||||
|
if n := len(handle); n > 1 {
|
||||||
|
lastI := n - 1
|
||||||
|
|
||||||
|
return func(ctx context.Context, operation string, item *Item, next Provider) error {
|
||||||
|
var (
|
||||||
|
chainHandler func(context.Context, string, *Item) error
|
||||||
|
curI int
|
||||||
|
)
|
||||||
|
|
||||||
|
chainHandler = func(currentCtx context.Context, currentOperation string, currentData *Item) error {
|
||||||
|
if curI == lastI {
|
||||||
|
return next(currentCtx, currentOperation, currentData)
|
||||||
|
}
|
||||||
|
curI++
|
||||||
|
err := handle[curI](currentCtx, currentOperation, currentData, chainHandler)
|
||||||
|
curI--
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle[0](ctx, operation, item, chainHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func chain(init Provider, handleFunc ...Handle) Provider {
|
||||||
|
if len(handleFunc) > 0 {
|
||||||
|
return func(ctx context.Context, operation string, item *Item) error {
|
||||||
|
return ChainHandle(handleFunc...)(ctx, operation, item, init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return init
|
||||||
|
}
|
||||||
194
provider/bench_provider_test.go
Normal file
194
provider/bench_provider_test.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package provider_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
glru "github.com/hashicorp/golang-lru"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/lru"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memcache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/pebble"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/redis"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/ristretto"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
type provider struct {
|
||||||
|
name string
|
||||||
|
prov cache.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func providers() []provider {
|
||||||
|
client, _ := glru.New(10000)
|
||||||
|
db, cl := test.PebbleDB()
|
||||||
|
|
||||||
|
defer cl()
|
||||||
|
|
||||||
|
return []provider{
|
||||||
|
{"encoding", memory.NewEncoding()},
|
||||||
|
{"map", memory.NewMap()},
|
||||||
|
{"shard", memory.NewMapShard()},
|
||||||
|
{"lru", lru.New(client)},
|
||||||
|
{"ristretto", ristretto.New(test.RistrettoClient())},
|
||||||
|
{"memcache", memcache.New(test.MemcacheClient())},
|
||||||
|
{"redis", redis.New(test.RedisClient())},
|
||||||
|
{"pebble", pebble.New(db)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randStringBytes(n int64) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letterBytes[randInt64(int64(len(letterBytes)))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randInt64(max int64) int64 {
|
||||||
|
m := big.NewInt(max)
|
||||||
|
nBig, _ := rand.Int(rand.Reader, m)
|
||||||
|
|
||||||
|
return nBig.Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheGetRandomKeyString(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
keysLen := 10000
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
items := make([]*cache.Item, keysLen)
|
||||||
|
|
||||||
|
for i := 0; i < keysLen; i++ {
|
||||||
|
var val string
|
||||||
|
|
||||||
|
key := randStringBytes(55)
|
||||||
|
items[i] = cache.NewItem(key, &val)
|
||||||
|
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem(key, "value: "+p.name, cache.WithTTL(time.Minute))))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = prov(ctx, cache.OperationGet, items[i%keysLen])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheGetRandomKeyInt(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
keysLen := 10000
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
items := make([]*cache.Item, keysLen)
|
||||||
|
|
||||||
|
for i := 0; i < keysLen; i++ {
|
||||||
|
var val int64
|
||||||
|
|
||||||
|
key := randInt64(math.MaxInt64)
|
||||||
|
|
||||||
|
items[i] = cache.NewItem(key, &val)
|
||||||
|
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem(key, randInt64(math.MaxInt64), cache.WithTTL(time.Minute))))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = prov(ctx, cache.OperationGet, items[i%keysLen])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheGetStruct(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
Key string
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
var val testStruct
|
||||||
|
item := cache.NewItem("key", &val)
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem("key", testStruct{Key: "key", val: ""}, cache.WithTTL(time.Minute))))
|
||||||
|
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = prov(ctx, cache.OperationGet, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheSetStruct(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
Key string
|
||||||
|
Val int
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
item := cache.NewItem(i, testStruct{"k", i}, cache.WithTTL(time.Hour))
|
||||||
|
_ = prov(ctx, cache.OperationSet, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheGetParallel(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
key := fmt.Sprintf("key_%s", p.name)
|
||||||
|
val := fmt.Sprintf("value_%s", p.name)
|
||||||
|
item := cache.NewItem(key, &val, cache.WithTTL(time.Minute))
|
||||||
|
require.Nil(b, prov(ctx, cache.OperationSet, item))
|
||||||
|
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_ = prov(ctx, cache.OperationGet, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheSetParallel(b *testing.B) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for _, p := range providers() {
|
||||||
|
prov := p.prov
|
||||||
|
key := fmt.Sprintf("key: %v", prov)
|
||||||
|
val := fmt.Sprintf("value: %v", prov)
|
||||||
|
|
||||||
|
b.Run(p.name, func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
item := cache.NewItem(key, val, cache.WithTTL(time.Hour))
|
||||||
|
_ = prov(ctx, cache.OperationSet, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
66
provider/lru/provider.go
Normal file
66
provider/lru/provider.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package lru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New create new lru cache provider.
|
||||||
|
func New(client *lru.Cache) cache.Provider {
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationGet:
|
||||||
|
val, ok := client.Get(item.Key)
|
||||||
|
if !ok {
|
||||||
|
return wrapErr(cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
it, _ := val.(expired)
|
||||||
|
|
||||||
|
if !it.ex.IsZero() {
|
||||||
|
item.TTL = time.Until(it.ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.IsExpired() {
|
||||||
|
return wrapErr(cache.ErrCacheExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.TypeAssert(it.value, item.Value))
|
||||||
|
case cache.OperationSet:
|
||||||
|
it := expired{
|
||||||
|
value: item.Value,
|
||||||
|
ex: time.Time{},
|
||||||
|
}
|
||||||
|
if item.TTL > 0 {
|
||||||
|
it.ex = item.Expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = client.Add(item.Key, it)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationDelete:
|
||||||
|
_ = client.Remove(item.Key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type expired struct {
|
||||||
|
ex time.Time
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErr(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: lru", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
provider/lru/provider_test.go
Normal file
18
provider/lru/provider_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package lru_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
glru "github.com/hashicorp/golang-lru"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/lru"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncoding(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, err := glru.New(10)
|
||||||
|
require.Nil(t, err)
|
||||||
|
test.RunSute(t, lru.New(client))
|
||||||
|
}
|
||||||
52
provider/memcache/provider.go
Normal file
52
provider/memcache/provider.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New memcache provider.
|
||||||
|
func New(client *memcache.Client) cache.Provider {
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
key := item.Key.String()
|
||||||
|
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationGet:
|
||||||
|
ci, err := client.Get(item.Key.String())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, memcache.ErrCacheMiss):
|
||||||
|
return wrapErr(cache.ErrCacheMiss)
|
||||||
|
case errors.Is(err, memcache.ErrMalformedKey):
|
||||||
|
return wrapErr(cache.ErrKeyNotValid)
|
||||||
|
case err != nil:
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(item.Unmarshal(ci.Value))
|
||||||
|
case cache.OperationSet:
|
||||||
|
data, err := item.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(client.Set(&memcache.Item{Key: key, Flags: 0, Value: data, Expiration: int32(item.TTL.Seconds())}))
|
||||||
|
case cache.OperationDelete:
|
||||||
|
return wrapErr(client.Delete(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErr(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: memcache", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
provider/memcache/provider_test.go
Normal file
14
provider/memcache/provider_test.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package memcache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memcache"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test.RunSute(t, memcache.New(test.MemcacheClient()), test.WithExpire(cache.ErrCacheMiss))
|
||||||
|
}
|
||||||
93
provider/memory/encoding.go
Normal file
93
provider/memory/encoding.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoding create new provider.
|
||||||
|
func NewEncoding() cache.Provider {
|
||||||
|
items := make(map[cache.Key]encodedEntry)
|
||||||
|
mu := sync.RWMutex{}
|
||||||
|
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationSet:
|
||||||
|
i, err := newEncodedEntry(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
items[item.Key] = i
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationDelete:
|
||||||
|
mu.Lock()
|
||||||
|
delete(items, item.Key)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationGet:
|
||||||
|
mu.RLock()
|
||||||
|
i, ok := items[item.Key]
|
||||||
|
mu.RUnlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return wrapErr(cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveEncodedEntry(i, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodedEntry struct {
|
||||||
|
data []byte
|
||||||
|
expired time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErr(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: encoding", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEncodedEntry(item *cache.Item) (encodedEntry, error) {
|
||||||
|
var (
|
||||||
|
e encodedEntry
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
e.data, err = item.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return e, wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.TTL > 0 {
|
||||||
|
e.expired = item.Expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveEncodedEntry(e encodedEntry, item *cache.Item) error {
|
||||||
|
if !e.expired.IsZero() {
|
||||||
|
item.TTL = time.Until(e.expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.IsExpired() {
|
||||||
|
return wrapErr(cache.ErrCacheExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(item.Unmarshal(e.data))
|
||||||
|
}
|
||||||
13
provider/memory/encoding_test.go
Normal file
13
provider/memory/encoding_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package memory_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncoding(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test.RunSute(t, memory.NewEncoding())
|
||||||
|
}
|
||||||
155
provider/memory/map.go
Normal file
155
provider/memory/map.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc64"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultShards = 255
|
||||||
|
|
||||||
|
// NewMap creates new map cache.
|
||||||
|
func NewMap() cache.Provider {
|
||||||
|
m := sync.Map{}
|
||||||
|
|
||||||
|
return func(ctx context.Context, op string, item *cache.Item) error {
|
||||||
|
switch op {
|
||||||
|
case cache.OperationDelete:
|
||||||
|
m.Delete(item.Key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationSet:
|
||||||
|
m.Store(item.Key, newEntry(item))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationGet:
|
||||||
|
e, ok := m.Load(item.Key)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: map", cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveEntry(e.(entry), item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: map", cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveEntry(e entry, item *cache.Item) error {
|
||||||
|
if !e.expired.IsZero() {
|
||||||
|
item.TTL = time.Until(e.expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.IsExpired() {
|
||||||
|
return fmt.Errorf("%w: map", cache.ErrCacheExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.TypeAssert(e.data, item.Value); err != nil {
|
||||||
|
return fmt.Errorf("%w: map", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntry(item *cache.Item) entry {
|
||||||
|
e := entry{data: item.Value}
|
||||||
|
|
||||||
|
if item.TTL > 0 {
|
||||||
|
e.expired = item.Expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
data interface{}
|
||||||
|
expired time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type settings struct {
|
||||||
|
numShards uint64
|
||||||
|
hashString func(in cache.Key) uint64
|
||||||
|
}
|
||||||
|
type Option func(*settings)
|
||||||
|
|
||||||
|
func WithNumShards(num uint64) Option {
|
||||||
|
return func(s *settings) {
|
||||||
|
s.numShards = num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithHashKey(f func(in cache.Key) uint64) Option {
|
||||||
|
return func(s *settings) {
|
||||||
|
s.hashString = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint: gochecknoglobals
|
||||||
|
var table = crc64.MakeTable(crc64.ISO)
|
||||||
|
|
||||||
|
func hashString(in cache.Key) uint64 {
|
||||||
|
switch k := in.Key.(type) {
|
||||||
|
case int64:
|
||||||
|
return uint64(k)
|
||||||
|
case int32:
|
||||||
|
return uint64(k)
|
||||||
|
case int:
|
||||||
|
return uint64(k)
|
||||||
|
case uint64:
|
||||||
|
return k
|
||||||
|
case uint32:
|
||||||
|
return uint64(k)
|
||||||
|
case uint:
|
||||||
|
return uint64(k)
|
||||||
|
default:
|
||||||
|
return crc64.Checksum([]byte(in.String()), table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapShard(opts ...Option) cache.Provider {
|
||||||
|
s := settings{
|
||||||
|
numShards: defaultShards,
|
||||||
|
hashString: hashString,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]*sync.Map, s.numShards)
|
||||||
|
for i := range items {
|
||||||
|
items[i] = &sync.Map{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
idx := s.hashString(item.Key)
|
||||||
|
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationDelete:
|
||||||
|
items[idx%s.numShards].Delete(item.Key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationSet:
|
||||||
|
items[idx%s.numShards].Store(item.Key, newEntry(item))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationGet:
|
||||||
|
e, ok := items[idx%s.numShards].Load(item.Key)
|
||||||
|
if !ok {
|
||||||
|
return wrapShardErr(cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveEntry(e.(entry), item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapShardErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapShardErr(err error) error {
|
||||||
|
return fmt.Errorf("%w: memory shards", err)
|
||||||
|
}
|
||||||
18
provider/memory/map_test.go
Normal file
18
provider/memory/map_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package memory_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/memory"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test.RunSute(t, memory.NewMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapShard(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test.RunSute(t, memory.NewMapShard())
|
||||||
|
}
|
||||||
22
provider/ns/provider.go
Normal file
22
provider/ns/provider.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package ns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrProviderNotFound = errors.New("provider not found")
|
||||||
|
|
||||||
|
func New(providers map[string]cache.Provider) cache.Provider {
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
if prov, ok := providers[item.Key.Prefix]; ok {
|
||||||
|
item.Key.Prefix = ""
|
||||||
|
|
||||||
|
return prov(ctx, operation, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrProviderNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
54
provider/pebble/provider.go
Normal file
54
provider/pebble/provider.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package pebble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/item"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(db *pebble.DB) cache.Provider {
|
||||||
|
return func(ctx context.Context, operation string, i *cache.Item) error {
|
||||||
|
key := []byte(i.Key.String())
|
||||||
|
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationGet:
|
||||||
|
val, cl, err := db.Get([]byte(i.Key.String()))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pebble.ErrNotFound) {
|
||||||
|
return wrapErr(cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = cl.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return wrapErr(item.UnmarshalExpired(i, val))
|
||||||
|
case cache.OperationSet:
|
||||||
|
b, err := item.MarshalExpired(i)
|
||||||
|
if err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(db.Set(key, b, pebble.Sync))
|
||||||
|
case cache.OperationDelete:
|
||||||
|
return wrapErr(db.Delete(key, pebble.Sync))
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErr(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: pebble", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
17
provider/pebble/provider_test.go
Normal file
17
provider/pebble/provider_test.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package pebble_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/pebble"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPebble(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, cl := test.PebbleDB()
|
||||||
|
defer cl()
|
||||||
|
|
||||||
|
test.RunSute(t, pebble.New(db))
|
||||||
|
}
|
||||||
110
provider/redis/pool.go
Normal file
110
provider/redis/pool.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn interface {
|
||||||
|
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||||
|
Send(commandName string, args ...interface{}) error
|
||||||
|
Flush() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates new provider.
|
||||||
|
func New(pool func(context.Context) (Conn, error)) cache.Provider {
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
conn, err := pool(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
key := item.Key.String()
|
||||||
|
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationGet:
|
||||||
|
data, ttl, err := get(conn, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
item.TTLInSecond(ttl)
|
||||||
|
|
||||||
|
return wrapErr(item.Unmarshal(data))
|
||||||
|
case cache.OperationSet:
|
||||||
|
data, err := item.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return set(conn, key, data, int(item.TTL.Seconds()))
|
||||||
|
case cache.OperationDelete:
|
||||||
|
return del(conn, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapErr(cache.ErrOperationNotAllwed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(conn Conn, key string) ([]byte, int64, error) {
|
||||||
|
data, err := conn.Do("GET", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
return nil, 0, wrapErr(cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := data.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, wrapErr(cache.ErrSourceNotValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
expire, err := conn.Do("TTL", key)
|
||||||
|
if err != nil {
|
||||||
|
return v, 0, wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ex, _ := expire.(int64)
|
||||||
|
|
||||||
|
return v, ex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(conn Conn, key string, data []byte, ttl int) error {
|
||||||
|
if err := conn.Send("SET", key, data); err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if err := conn.Send("EXPIRE", key, ttl); err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.Flush(); err != nil {
|
||||||
|
return fmt.Errorf("failed flush then set %s by %w", key, conn.Flush())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func del(conn Conn, key string) error {
|
||||||
|
if _, err := conn.Do("DEL", key); err != nil {
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErr(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: redis pool", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
provider/redis/pool_test.go
Normal file
14
provider/redis/pool_test.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package redis_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/redis"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisPool(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test.RunSute(t, redis.New(test.RedisClient()), test.WithExpire(cache.ErrCacheMiss))
|
||||||
|
}
|
||||||
20
provider/redis/redigo.go
Normal file
20
provider/redis/redigo.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPool creates redigo pool.
|
||||||
|
func NewPool(pool *redis.Pool) func(context.Context) (Conn, error) {
|
||||||
|
return func(ctx context.Context) (Conn, error) {
|
||||||
|
conn, err := pool.GetContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed get connect: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
69
provider/ristretto/provider.go
Normal file
69
provider/ristretto/provider.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package ristretto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/ristretto"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrSetValue = errors.New("failed set value")
|
||||||
|
|
||||||
|
type Option func(*setting)
|
||||||
|
|
||||||
|
func WithCost(cost int64) Option {
|
||||||
|
return func(s *setting) {
|
||||||
|
s.cost = cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type setting struct {
|
||||||
|
cost int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(retto *ristretto.Cache, opts ...Option) cache.Provider {
|
||||||
|
s := setting{
|
||||||
|
cost: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
var key interface{}
|
||||||
|
if item.Key.Prefix != "" {
|
||||||
|
key = item.Key.String()
|
||||||
|
} else {
|
||||||
|
key = item.Key.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
switch operation {
|
||||||
|
case cache.OperationGet:
|
||||||
|
res, ok := retto.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: ristretto", cache.ErrCacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.TypeAssert(res, item.Value); err != nil {
|
||||||
|
return fmt.Errorf("failed assert type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationDelete:
|
||||||
|
retto.Del(key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case cache.OperationSet:
|
||||||
|
if ok := retto.SetWithTTL(key, item.Value, s.cost, item.TTL); !ok {
|
||||||
|
return ErrSetValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.ErrOperationNotAllwed
|
||||||
|
}
|
||||||
|
}
|
||||||
26
provider/ristretto/provider_test.go
Normal file
26
provider/ristretto/provider_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package ristretto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/ristretto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
provider "gitoa.ru/go-4devs/cache/provider/ristretto"
|
||||||
|
"gitoa.ru/go-4devs/cache/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRistretto(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
retto, err := ristretto.NewCache(&ristretto.Config{
|
||||||
|
NumCounters: 1e7, // number of keys to track frequency of (10M).
|
||||||
|
MaxCost: 1 << 30, // maximum cost of cache (1GB).
|
||||||
|
BufferItems: 64, // number of keys per Get buffer.
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
test.RunSute(t, provider.New(retto), test.WithWaitGet(func() {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}), test.WithExpire(cache.ErrCacheMiss))
|
||||||
|
}
|
||||||
84
test/helpers.go
Normal file
84
test/helpers.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gom "github.com/bradfitz/gomemcache/memcache"
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
"github.com/dgraph-io/ristretto"
|
||||||
|
redigo "github.com/gomodule/redigo/redis"
|
||||||
|
"gitoa.ru/go-4devs/cache/provider/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisClient created redis client.
|
||||||
|
func RedisClient() func(ctx context.Context) (redis.Conn, error) {
|
||||||
|
host, ok := os.LookupEnv("FDEVS_CACHE_REDIS_HOST")
|
||||||
|
if !ok {
|
||||||
|
host = ":6379"
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &redigo.Pool{
|
||||||
|
DialContext: func(ctx context.Context) (redigo.Conn, error) {
|
||||||
|
return redigo.DialContext(ctx, "tcp", host)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return redis.NewPool(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemcacheClient created memcached client.
|
||||||
|
func MemcacheClient() *gom.Client {
|
||||||
|
host, ok := os.LookupEnv("FDEVS_CACHE_MEMCACHE_HOST")
|
||||||
|
if !ok {
|
||||||
|
host = "localhost:11211"
|
||||||
|
}
|
||||||
|
|
||||||
|
return gom.New(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RistrettoClient creates ristretto client.
|
||||||
|
func RistrettoClient() *ristretto.Cache {
|
||||||
|
cache, _ := ristretto.NewCache(&ristretto.Config{
|
||||||
|
NumCounters: 1e7, // number of keys to track frequency of (10M).
|
||||||
|
MaxCost: 1 << 30, // maximum cost of cache (1GB).
|
||||||
|
BufferItems: 64, // number of keys per Get buffer.
|
||||||
|
})
|
||||||
|
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// PebbleDB creates pebble DB.
|
||||||
|
func PebbleDB() (*pebble.DB, func()) {
|
||||||
|
path := "demo.test"
|
||||||
|
if _, filename, _, ok := runtime.Caller(0); ok {
|
||||||
|
path = filepath.Dir(filename) + "/" + path
|
||||||
|
}
|
||||||
|
|
||||||
|
db, _ := pebble.Open(path, &pebble.Options{})
|
||||||
|
|
||||||
|
return db, func() {
|
||||||
|
os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User tested user data.
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
UpdateAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUser create mocks data user.
|
||||||
|
func NewUser(id int) User {
|
||||||
|
return User{
|
||||||
|
ID: id,
|
||||||
|
Name: "andrey",
|
||||||
|
UpdateAt: time.Date(2020, 2, 1, 1, 2, 3, 4, time.UTC),
|
||||||
|
CreatedAt: time.Date(1999, 2, 1, 1, 2, 3, 4, time.UTC),
|
||||||
|
}
|
||||||
|
}
|
||||||
57
test/provider.go
Normal file
57
test/provider.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ cache.Provider = NewProviderMock(&testing.T{})
|
||||||
|
|
||||||
|
// OptionMock configure mock.
|
||||||
|
type OptionMock func(*ProviderMock)
|
||||||
|
|
||||||
|
// WithDelete sets delete method.
|
||||||
|
func WithDelete(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock {
|
||||||
|
return func(pm *ProviderMock) { pm.operations[cache.OperationDelete] = f }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGet sets get method.
|
||||||
|
func WithGet(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock {
|
||||||
|
return func(pm *ProviderMock) { pm.operations[cache.OperationGet] = f }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSet sets set method.
|
||||||
|
func WithSet(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock {
|
||||||
|
return func(pm *ProviderMock) { pm.operations[cache.OperationSet] = f }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProviderMock create new mock provider.
|
||||||
|
func NewProviderMock(t *testing.T, opts ...OptionMock) cache.Provider {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
pm := &ProviderMock{
|
||||||
|
t: t,
|
||||||
|
operations: make(map[string]func(t *testing.T) func(ctx context.Context, item *cache.Item) error, 3),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(pm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, operation string, item *cache.Item) error {
|
||||||
|
if m, ok := pm.operations[operation]; ok {
|
||||||
|
return m(pm.t)(ctx, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: %s", cache.ErrOperationNotAllwed, operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderMock mock.
|
||||||
|
type ProviderMock struct {
|
||||||
|
t *testing.T
|
||||||
|
operations map[string]func(t *testing.T) func(ctx context.Context, item *cache.Item) error
|
||||||
|
}
|
||||||
133
test/sute.go
Normal file
133
test/sute.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"gitoa.ru/go-4devs/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
expire = time.Second
|
||||||
|
waitExpire = expire * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option configure sute.
|
||||||
|
type Option func(*ProviderSuite)
|
||||||
|
|
||||||
|
// WithExpire sets expired errors.
|
||||||
|
func WithExpire(err error) Option {
|
||||||
|
return func(ps *ProviderSuite) { ps.expire = err }
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWaitGet(f func()) Option {
|
||||||
|
return func(ps *ProviderSuite) { ps.waitGet = f }
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunSute run test by provider.
|
||||||
|
func RunSute(t *testing.T, provider cache.Provider, opts ...Option) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cs := &ProviderSuite{
|
||||||
|
provider: provider,
|
||||||
|
expire: cache.ErrCacheExpired,
|
||||||
|
waitGet: func() {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.Run(t, cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderSuite for testing providers.
|
||||||
|
type ProviderSuite struct {
|
||||||
|
provider cache.Provider
|
||||||
|
expire error
|
||||||
|
waitGet func()
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGet tested get.
|
||||||
|
func (s *ProviderSuite) TestGet() {
|
||||||
|
s.T().Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var val string
|
||||||
|
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("get", "some value")))
|
||||||
|
s.waitGet()
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("get", &val)))
|
||||||
|
require.Equal(s.T(), "some value", val)
|
||||||
|
|
||||||
|
var user User
|
||||||
|
|
||||||
|
cachedUser := NewUser(1)
|
||||||
|
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("get_user", cachedUser)))
|
||||||
|
s.waitGet()
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("get_user", &user)))
|
||||||
|
require.Equal(s.T(), cachedUser, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCacheMiss tested cache miss error.
|
||||||
|
func (s *ProviderSuite) TestCacheMiss() {
|
||||||
|
s.T().Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
require.True(s.T(),
|
||||||
|
errors.Is(s.provider(ctx, cache.OperationGet, cache.NewItem("cache_miss", nil)), cache.ErrCacheMiss),
|
||||||
|
"failed expect errors",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExpired tested error expired.
|
||||||
|
func (s *ProviderSuite) TestExpired() {
|
||||||
|
s.T().Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var val string
|
||||||
|
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("expired", "some value", cache.WithTTL(expire))))
|
||||||
|
time.Sleep(waitExpire)
|
||||||
|
|
||||||
|
err := s.provider(ctx, cache.OperationGet, cache.NewItem("expired", nil))
|
||||||
|
require.Truef(s.T(), errors.Is(err, s.expire), "failed expired error got:%s", err)
|
||||||
|
require.Equal(s.T(), "", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTTL tested set ttl.
|
||||||
|
func (s *ProviderSuite) TestTTL() {
|
||||||
|
s.T().Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var val string
|
||||||
|
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("ttl", "some ttl value", cache.WithTTL(time.Hour))))
|
||||||
|
s.waitGet()
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("ttl", &val)))
|
||||||
|
require.Equal(s.T(), "some ttl value", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDelete tested delete method.
|
||||||
|
func (s *ProviderSuite) TestDelete() {
|
||||||
|
s.T().Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("delete:key", "some delete value")))
|
||||||
|
require.Nil(s.T(), s.provider(ctx, cache.OperationDelete, cache.NewItem("delete:key", nil)))
|
||||||
|
require.True(s.T(),
|
||||||
|
errors.Is(s.provider(ctx, cache.OperationGet, cache.NewItem("cache_miss", nil)), cache.ErrCacheMiss),
|
||||||
|
"failed delete errors",
|
||||||
|
)
|
||||||
|
}
|
||||||
99
type_assert.go
Normal file
99
type_assert.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypeAssert assert source to target.
|
||||||
|
func TypeAssert(source, target interface{}) (err error) {
|
||||||
|
if source == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if directTypeAssert(source, target) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(target)
|
||||||
|
if !v.IsValid() {
|
||||||
|
return ErrTargetNil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
return NewErrorTarget(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v.Elem()
|
||||||
|
|
||||||
|
if !v.IsValid() {
|
||||||
|
return NewErrorTarget(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := reflect.ValueOf(source)
|
||||||
|
if !s.IsValid() {
|
||||||
|
return ErrSourceNotValid
|
||||||
|
}
|
||||||
|
|
||||||
|
s = deReference(s)
|
||||||
|
v.Set(s)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: cyclop,gocyclo
|
||||||
|
func directTypeAssert(source, target interface{}) bool {
|
||||||
|
var ok bool
|
||||||
|
switch v := target.(type) {
|
||||||
|
case *string:
|
||||||
|
*v, ok = source.(string)
|
||||||
|
case *[]byte:
|
||||||
|
*v, ok = source.([]byte)
|
||||||
|
case *int:
|
||||||
|
*v, ok = source.(int)
|
||||||
|
case *int8:
|
||||||
|
*v, ok = source.(int8)
|
||||||
|
case *int16:
|
||||||
|
*v, ok = source.(int16)
|
||||||
|
case *int32:
|
||||||
|
*v, ok = source.(int32)
|
||||||
|
case *int64:
|
||||||
|
*v, ok = source.(int64)
|
||||||
|
case *uint:
|
||||||
|
*v, ok = source.(uint)
|
||||||
|
case *uint8:
|
||||||
|
*v, ok = source.(uint8)
|
||||||
|
case *uint16:
|
||||||
|
*v, ok = source.(uint16)
|
||||||
|
case *uint32:
|
||||||
|
*v, ok = source.(uint32)
|
||||||
|
case *uint64:
|
||||||
|
*v, ok = source.(uint64)
|
||||||
|
case *bool:
|
||||||
|
*v, ok = source.(bool)
|
||||||
|
case *float32:
|
||||||
|
*v, ok = source.(float32)
|
||||||
|
case *float64:
|
||||||
|
*v, ok = source.(float64)
|
||||||
|
case *time.Duration:
|
||||||
|
*v, ok = source.(time.Duration)
|
||||||
|
case *time.Time:
|
||||||
|
*v, ok = source.(time.Time)
|
||||||
|
case *[]string:
|
||||||
|
*v, ok = source.([]string)
|
||||||
|
case *map[string]string:
|
||||||
|
*v, ok = source.(map[string]string)
|
||||||
|
case *map[string]interface{}:
|
||||||
|
*v, ok = source.(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func deReference(v reflect.Value) reflect.Value {
|
||||||
|
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() {
|
||||||
|
return v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user