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