Compare commits

...

102 Commits

Author SHA1 Message Date
Kevin Wan
0cac41a38b chore: refactor mapping unmarshaler (#4145) 2024-05-12 14:37:36 +08:00
Kevin Wan
f10084a3f5 chore: refactor and coding style (#4144) 2024-05-11 23:06:59 +08:00
Leo
040fee5669 feat: httpx.Parse supports parsing structures that implement the Unmarshaler interface (#4143) 2024-05-11 22:25:10 +08:00
Kevin Wan
42b3bae65a optimize: improve breaker algorithm on recovery time (#4141) 2024-05-11 21:44:26 +08:00
guangwu
7c730b97d8 fix: make: command: Command not found (#4132)
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-05-10 13:33:03 +00:00
Kevin Wan
057bae92ab fix: log panic on Error() or String() panics (#4136) 2024-05-10 12:49:34 +08:00
Kevin Wan
74331a45c9 fix: log panic when use nil error or stringer with Field method (#4130) 2024-05-10 00:31:36 +08:00
chen quan
9d551d507f chore(api/maxconnshandler): add tracing information to the log (#4126) 2024-05-08 05:25:35 +00:00
Kevin Wan
02dd81c05c Update FUNDING.yml (#4128) 2024-05-08 12:52:42 +08:00
dependabot[bot]
3095ba2b1f chore(deps): bump golang.org/x/net from 0.24.0 to 0.25.0 (#4124)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 12:52:13 +08:00
dependabot[bot]
2afa60132c chore(deps): bump google.golang.org/protobuf from 1.34.0 to 1.34.1 (#4123)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 12:45:10 +08:00
Kevin Wan
e71ed7294b Update FUNDING.yml (#4127) 2024-05-08 12:26:41 +08:00
dependabot[bot]
95822281bf chore(deps): bump golang.org/x/sys from 0.19.0 to 0.20.0 (#4122)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 12:22:06 +08:00
Kevin Wan
588e10daef chore: refactor and coding style (#4120) 2024-05-06 18:16:56 +08:00
soasurs
62ba01120e fix: zrpc kube resolver builder (#4119)
Signed-off-by: soasurs <soasurs@gmail.com>
2024-05-06 14:50:35 +08:00
dependabot[bot]
527de1c50e chore(deps): bump google.golang.org/protobuf from 1.33.1-0.20240408130810-98873a205002 to 1.34.0 (#4115)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-04 23:59:47 +08:00
dependabot[bot]
abfe62a2d7 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.2.1 to 2.2.2 (#4116)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-04 23:39:25 +08:00
Kevin Wan
36f4cf97ff Update FUNDING.yml (#4114) 2024-04-30 22:58:51 +08:00
Kevin Wan
b3cd8a32ed feat: trigger breaker on underlying service timeout (#4112) 2024-04-30 19:01:20 +08:00
kesonan
a9d27cda8a (goctl): fix prefix syntax (#4113) 2024-04-30 09:11:08 +00:00
kesonan
04116f647d chore(goctl): change goctl version to 1.6.5 (#4111) 2024-04-30 04:25:47 +00:00
Kevin Wan
a8ccda0c06 feat: add fx.ParallelErr (#4107) 2024-04-29 00:18:30 +08:00
Kevin Wan
bfddb9dae4 feat: add errorx.In to facility error checking (#4105) 2024-04-27 20:43:45 +08:00
Kevin Wan
b337ae36e5 Update readme-cn.md 2024-04-20 10:00:10 +08:00
Kevin Wan
5e5123caa3 chore: add more tests (#4094) 2024-04-19 11:13:23 +08:00
Kevin Wan
d371ab5479 feat: use breaker with ctx to prevent deadline exceeded (#4091)
Signed-off-by: kevin <wanjunfeng@gmail.com>
2024-04-18 23:18:49 +08:00
jaron
1b9b61f505 fix(goctl): GOPROXY env should set by ourself (#4087) 2024-04-18 22:50:30 +08:00
suyhuai
e1f15efb3b add customized.tpl for model template (#4086)
Co-authored-by: sudaoxyz <sudaoxyz@gmail.com>
2024-04-18 14:40:54 +00:00
Kevin Wan
1540bdc4c9 optimize: improve breaker algorithm on recovery time (#4077) 2024-04-18 22:33:25 +08:00
Kevin Wan
95b32b5779 chore: add code coverage (#4090) 2024-04-18 20:58:36 +08:00
Kevin Wan
815a4f7eed feat: support context in breaker methods (#4088) 2024-04-18 18:00:17 +08:00
dependabot[bot]
4b0bacc9c6 chore(deps): bump k8s.io/apimachinery from 0.29.3 to 0.29.4 (#4084)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 11:22:49 +08:00
Kevin Wan
e9dc96af17 chore: coding style (#4082) 2024-04-17 23:37:35 +08:00
fearlessfei
62c88a84d1 feat: migrate lua script to lua file (#4069) 2024-04-17 15:20:10 +00:00
Kevin Wan
36088ea0d4 fix: avoid duplicate in logx plain mode (#4080) 2024-04-17 17:43:22 +08:00
dependabot[bot]
164f5aa86c chore(deps): bump github.com/pelletier/go-toml/v2 from 2.2.0 to 2.2.1 (#4073)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-13 12:32:56 +08:00
dependabot[bot]
07d07cdd23 chore(deps): bump github.com/fullstorydev/grpcurl from 1.8.9 to 1.9.1 (#4065)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 13:02:52 +08:00
dependabot[bot]
0efe99af66 chore(deps): bump github.com/jhump/protoreflect from 1.15.6 to 1.16.0 (#4064)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 12:39:00 +08:00
Kevin Wan
927f8bc821 fix: fix ignored context.DeadlineExceeded (#4066) 2024-04-11 11:14:20 +08:00
kesonan
2a7ada993b (goctl)feature/model config (#4062)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2024-04-10 15:01:59 +00:00
Kevin Wan
682460c1c8 fix: fix ignored scanner.Err() (#4063) 2024-04-10 17:28:52 +08:00
Kevin Wan
a66ae0d4c4 fix: timeout on query should return context.DeadlineExceeded (#4060) 2024-04-10 04:17:39 +00:00
dependabot[bot]
d1f24ab70f chore(deps): bump google.golang.org/grpc from 1.63.0 to 1.63.2 (#4058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 10:23:33 +08:00
dependabot[bot]
d0983948b5 chore(deps): bump google.golang.org/grpc from 1.63.0 to 1.63.2 in /tools/goctl (#4059)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 10:14:15 +08:00
Kevin Wan
3343fc2cdb chore: update goctl version to 1.6.4 (#4057) 2024-04-09 22:59:33 +08:00
Kevin Wan
3866b5741a feat: support http stream response (#4055) 2024-04-09 20:46:44 +08:00
Kevin Wan
5fbe8ff5c4 chore: coding style (#4054) 2024-04-09 17:19:47 +08:00
jaron
6f763f71f9 chore(goctl): update readme (#4053) 2024-04-09 08:30:25 +00:00
dependabot[bot]
80377f18e7 chore(deps): bump codecov/codecov-action from 3 to 4 (#4051)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 15:24:24 +08:00
dependabot[bot]
8690859c7d chore(deps): bump golang.org/x/net from 0.23.0 to 0.24.0 (#4048)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-06 10:03:06 +08:00
dependabot[bot]
d744038198 chore(deps): bump google.golang.org/grpc from 1.62.1 to 1.63.0 (#4045)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 19:43:17 +08:00
dependabot[bot]
58ad8cac8a chore(deps): bump golang.org/x/sys from 0.18.0 to 0.19.0 (#4046)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 19:28:54 +08:00
dependabot[bot]
74886a151e chore(deps): bump google.golang.org/grpc from 1.62.1 to 1.63.0 in /tools/goctl (#4047)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 18:45:49 +08:00
Kevin Wan
c5eda1f155 chore: fix codecov (#4044) 2024-04-05 00:53:13 +08:00
Kevin Wan
b5b7c054ca chore: fix codecov (#4043) 2024-04-05 00:43:38 +08:00
Kevin Wan
6c8073b691 chore: add more tests (#4042) 2024-04-05 00:13:42 +08:00
Kevin Wan
64d430d424 fix: bug on form data with slices (#4040) 2024-04-04 20:28:54 +08:00
Jayson Wang
f138cc792e fix(goctl): multi imports the api cause redeclared error in types.go (#3988)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2024-04-04 11:39:24 +00:00
dependabot[bot]
b20ec8aedb chore(deps): bump golang.org/x/net from 0.22.0 to 0.23.0 (#4039)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 10:48:52 +08:00
Kevin Wan
a53254fa91 chore: update codecov config (#4038) 2024-04-03 23:58:02 +08:00
Kevin Wan
08563482e5 chore: coding style (#4037) 2024-04-03 22:55:52 +08:00
fearlessfei
968727412d add custom health response information (#4034)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2024-04-03 14:33:55 +00:00
linden-in-China
6f3d094eba opton to option (#4035) 2024-04-03 14:15:21 +00:00
kesonan
2d3ebb9b62 (goctl) fix #4027 (#4032) 2024-04-01 15:22:29 +00:00
chentong
8c0bb27136 feat: add gen api @doc comment to logic handler routes (#3790)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2024-03-30 11:09:54 +00:00
ak5w
cf987295df fix the usage datasource url of postgresql (#4029) (#4030) 2024-03-30 05:51:54 +00:00
dependabot[bot]
8c92b3af7d chore(deps): bump go.etcd.io/etcd/client/v3 from 3.5.12 to 3.5.13 (#4028)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-30 13:40:24 +08:00
Kevin Wan
5dd9342703 chore: fix test failure (#4031) 2024-03-30 13:29:58 +08:00
shyandsy
3ef59f6a71 fix(httpx): support array field for request dto (#4026)
Co-authored-by: yshi3 <yshi3@tesla.com>
2024-03-30 12:10:56 +08:00
dependabot[bot]
f12802abc7 chore(deps): bump github.com/go-sql-driver/mysql from 1.8.0 to 1.8.1 (#4022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 18:42:35 +08:00
dependabot[bot]
6f0fe67804 chore(deps): bump github.com/go-sql-driver/mysql from 1.8.0 to 1.8.1 in /tools/goctl (#4023)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 18:07:00 +08:00
dependabot[bot]
f44f0e7e62 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.1.1 to 2.2.0 (#4017)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 23:34:06 +08:00
dependabot[bot]
cdd95296db chore(deps): bump k8s.io/client-go from 0.29.2 to 0.29.3 (#4012)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 15:41:20 +08:00
kesonan
3e794cf991 (goctl)fix code_ql (#4009) 2024-03-17 02:21:36 +00:00
Kevin Wan
bbce95e7e1 fix: didn't count failure in allow method with breaker algorithm (#4008) 2024-03-16 22:19:36 +08:00
dependabot[bot]
0449450c64 chore(deps): bump github.com/jackc/pgx/v5 from 5.5.3 to 5.5.4 in /tools/goctl (#4007)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 12:55:57 +08:00
dependabot[bot]
9f9a12ea57 chore(deps): bump github.com/alicebob/miniredis/v2 from 2.31.1 to 2.32.1 (#4003)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 11:19:44 +08:00
Kevin Wan
cc2a7e97f9 chore: coding style, add code for prometheus (#4002) 2024-03-13 20:00:35 +08:00
dependabot[bot]
09d7af76af chore(deps): bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.0 in /tools/goctl (#3997)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-13 13:28:27 +08:00
dependabot[bot]
c233a66601 chore(deps): bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.0 (#3998)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-13 12:56:41 +08:00
dependabot[bot]
94fa12560c chore(deps): bump github.com/jackc/pgx/v5 from 5.5.4 to 5.5.5 (#3999)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 12:28:28 +08:00
MarkJoyMa
7d90f906f5 feat: migrate redis breaker into hook (#3982) 2024-03-12 04:21:33 +00:00
Viktor Patchev
f372b98d96 Add: Optimize the error log to be more specific (#3994) 2024-03-11 13:06:50 +08:00
mongobaba
459d3025c5 optimize: change err == xx to errors.Is(err, xx) (#3991) 2024-03-09 12:49:16 +00:00
Kevin Wan
e9e55125a9 chore: fix warnings (#3990) 2024-03-09 13:48:11 +08:00
Kevin Wan
159ecb7386 chore: fix warnings (#3989) 2024-03-08 22:35:17 +08:00
ansoda
69bb746a1d fix: StopAgent panics when trace agent disabled (#3981)
Co-authored-by: ansoda <ansoda@gmail.com>
2024-03-08 10:28:23 +00:00
Kevin Wan
d184f96b13 chore: coding style (#3987) 2024-03-08 16:11:28 +08:00
MarkJoyMa
c7dacb0146 fix: mysql WithAcceptable bug (#3986) 2024-03-08 04:23:41 +00:00
dependabot[bot]
2207477b60 chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 in /tools/goctl (#3978)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 11:15:48 +08:00
dependabot[bot]
105ab590ff chore(deps): bump google.golang.org/grpc from 1.62.0 to 1.62.1 in /tools/goctl (#3977)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 11:01:14 +08:00
dependabot[bot]
2f4c58ed73 chore(deps): bump google.golang.org/grpc from 1.62.0 to 1.62.1 (#3976)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 10:45:19 +08:00
dependabot[bot]
1631aa02ad chore(deps): bump github.com/golang/protobuf from 1.5.3 to 1.5.4 (#3984)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 10:24:51 +08:00
dependabot[bot]
4df10eef5d chore(deps): bump golang.org/x/net from 0.21.0 to 0.22.0 (#3975)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-06 23:31:02 +08:00
dependabot[bot]
3d552ea7a8 chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#3974)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-06 21:33:45 +08:00
Kevin Wan
74b87ac9fd chore: coding style (#3972) 2024-03-05 14:40:10 +08:00
Alex Last
ba1d6e3664 fix: only add log middleware to not found handler when enabled (#3969) 2024-03-05 04:14:54 +00:00
dependabot[bot]
2096cd5749 chore(deps): bump github.com/jackc/pgx/v5 from 5.5.3 to 5.5.4 (#3970)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-05 12:09:18 +08:00
dependabot[bot]
2eb2fa26f6 chore(deps): bump golang.org/x/sys from 0.17.0 to 0.18.0 (#3971)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-05 12:03:06 +08:00
Kevin Wan
bc4187ca90 Create SECURITY.md (#3968) 2024-03-04 23:07:54 +08:00
Kevin Wan
b7be25b98b Update readme-cn.md (#3966) 2024-03-03 14:18:27 +08:00
Kevin Wan
dd01695d45 chore: update goctl version to 1.6.3 (#3965) 2024-03-03 13:36:35 +08:00
172 changed files with 4141 additions and 2267 deletions

12
.github/FUNDING.yml vendored
View File

@@ -1,13 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: kevwan
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
ethereum: # 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan

16
SECURITY.md Normal file
View File

@@ -0,0 +1,16 @@
# Security Policy
## Supported Versions
We publish releases monthly.
| Version | Supported |
| ------- | ------------------ |
| >= 1.4.4 | :white_check_mark: |
| < 1.4.4 | :x: |
## Reporting a Vulnerability
https://github.com/zeromicro/go-zero/security/advisories
Accepted vulnerabilities are expected to be fixed within a month.

View File

@@ -2,6 +2,7 @@ package bloom
import ( import (
"context" "context"
_ "embed"
"errors" "errors"
"strconv" "strconv"
@@ -17,19 +18,13 @@ var (
// ErrTooLargeOffset indicates the offset is too large in bitset. // ErrTooLargeOffset indicates the offset is too large in bitset.
ErrTooLargeOffset = errors.New("too large offset") ErrTooLargeOffset = errors.New("too large offset")
setScript = redis.NewScript(` //go:embed setscript.lua
for _, offset in ipairs(ARGV) do setLuaScript string
redis.call("setbit", KEYS[1], offset, 1) setScript = redis.NewScript(setLuaScript)
end
`) //go:embed testscript.lua
testScript = redis.NewScript(` testLuaScript string
for _, offset in ipairs(ARGV) do testScript = redis.NewScript(testLuaScript)
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
return true
`)
) )
type ( type (
@@ -130,7 +125,7 @@ func (r *redisBitSet) check(ctx context.Context, offsets []uint) (bool, error) {
} }
resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args) resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args)
if err == redis.Nil { if errors.Is(err, redis.Nil) {
return false, nil return false, nil
} else if err != nil { } else if err != nil {
return false, err return false, err
@@ -162,7 +157,7 @@ func (r *redisBitSet) set(ctx context.Context, offsets []uint) error {
} }
_, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args) _, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args)
if err == redis.Nil { if errors.Is(err, redis.Nil) {
return nil return nil
} }

3
core/bloom/setscript.lua Normal file
View File

@@ -0,0 +1,3 @@
for _, offset in ipairs(ARGV) do
redis.call("setbit", KEYS[1], offset, 1)
end

View File

@@ -0,0 +1,6 @@
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
return true

View File

@@ -1,6 +1,7 @@
package breaker package breaker
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@@ -31,16 +32,21 @@ type (
Name() string Name() string
// Allow checks if the request is allowed. // Allow checks if the request is allowed.
// If allowed, a promise will be returned, the caller needs to call promise.Accept() // If allowed, a promise will be returned,
// on success, or call promise.Reject() on failure. // otherwise ErrServiceUnavailable will be returned as the error.
// If not allow, ErrServiceUnavailable will be returned. // The caller needs to call promise.Accept() on success,
// or call promise.Reject() on failure.
Allow() (Promise, error) Allow() (Promise, error)
// AllowCtx checks if the request is allowed when ctx isn't done.
AllowCtx(ctx context.Context) (Promise, error)
// Do runs the given request if the Breaker accepts it. // Do runs the given request if the Breaker accepts it.
// Do returns an error instantly if the Breaker rejects the request. // Do returns an error instantly if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
Do(req func() error) error Do(req func() error) error
// DoCtx runs the given request if the Breaker accepts it when ctx isn't done.
DoCtx(ctx context.Context, req func() error) error
// DoWithAcceptable runs the given request if the Breaker accepts it. // DoWithAcceptable runs the given request if the Breaker accepts it.
// DoWithAcceptable returns an error instantly if the Breaker rejects the request. // DoWithAcceptable returns an error instantly if the Breaker rejects the request.
@@ -48,12 +54,16 @@ type (
// and causes the same panic again. // and causes the same panic again.
// acceptable checks if it's a successful call, even if the error is not nil. // acceptable checks if it's a successful call, even if the error is not nil.
DoWithAcceptable(req func() error, acceptable Acceptable) error DoWithAcceptable(req func() error, acceptable Acceptable) error
// DoWithAcceptableCtx runs the given request if the Breaker accepts it when ctx isn't done.
DoWithAcceptableCtx(ctx context.Context, req func() error, acceptable Acceptable) error
// DoWithFallback runs the given request if the Breaker accepts it. // DoWithFallback runs the given request if the Breaker accepts it.
// DoWithFallback runs the fallback if the Breaker rejects the request. // DoWithFallback runs the fallback if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
DoWithFallback(req func() error, fallback Fallback) error DoWithFallback(req func() error, fallback Fallback) error
// DoWithFallbackCtx runs the given request if the Breaker accepts it when ctx isn't done.
DoWithFallbackCtx(ctx context.Context, req func() error, fallback Fallback) error
// DoWithFallbackAcceptable runs the given request if the Breaker accepts it. // DoWithFallbackAcceptable runs the given request if the Breaker accepts it.
// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request. // DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
@@ -61,6 +71,9 @@ type (
// and causes the same panic again. // and causes the same panic again.
// acceptable checks if it's a successful call, even if the error is not nil. // acceptable checks if it's a successful call, even if the error is not nil.
DoWithFallbackAcceptable(req func() error, fallback Fallback, acceptable Acceptable) error DoWithFallbackAcceptable(req func() error, fallback Fallback, acceptable Acceptable) error
// DoWithFallbackAcceptableCtx runs the given request if the Breaker accepts it when ctx isn't done.
DoWithFallbackAcceptableCtx(ctx context.Context, req func() error, fallback Fallback,
acceptable Acceptable) error
} }
// Fallback is the func to be called if the request is rejected. // Fallback is the func to be called if the request is rejected.
@@ -117,23 +130,71 @@ func (cb *circuitBreaker) Allow() (Promise, error) {
return cb.throttle.allow() return cb.throttle.allow()
} }
func (cb *circuitBreaker) AllowCtx(ctx context.Context) (Promise, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
return cb.Allow()
}
}
func (cb *circuitBreaker) Do(req func() error) error { func (cb *circuitBreaker) Do(req func() error) error {
return cb.throttle.doReq(req, nil, defaultAcceptable) return cb.throttle.doReq(req, nil, defaultAcceptable)
} }
func (cb *circuitBreaker) DoCtx(ctx context.Context, req func() error) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return cb.Do(req)
}
}
func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error { func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {
return cb.throttle.doReq(req, nil, acceptable) return cb.throttle.doReq(req, nil, acceptable)
} }
func (cb *circuitBreaker) DoWithAcceptableCtx(ctx context.Context, req func() error,
acceptable Acceptable) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return cb.DoWithAcceptable(req, acceptable)
}
}
func (cb *circuitBreaker) DoWithFallback(req func() error, fallback Fallback) error { func (cb *circuitBreaker) DoWithFallback(req func() error, fallback Fallback) error {
return cb.throttle.doReq(req, fallback, defaultAcceptable) return cb.throttle.doReq(req, fallback, defaultAcceptable)
} }
func (cb *circuitBreaker) DoWithFallbackCtx(ctx context.Context, req func() error,
fallback Fallback) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return cb.DoWithFallback(req, fallback)
}
}
func (cb *circuitBreaker) DoWithFallbackAcceptable(req func() error, fallback Fallback, func (cb *circuitBreaker) DoWithFallbackAcceptable(req func() error, fallback Fallback,
acceptable Acceptable) error { acceptable Acceptable) error {
return cb.throttle.doReq(req, fallback, acceptable) return cb.throttle.doReq(req, fallback, acceptable)
} }
func (cb *circuitBreaker) DoWithFallbackAcceptableCtx(ctx context.Context, req func() error,
fallback Fallback, acceptable Acceptable) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return cb.DoWithFallbackAcceptable(req, fallback, acceptable)
}
}
func (cb *circuitBreaker) Name() string { func (cb *circuitBreaker) Name() string {
return cb.name return cb.name
} }

View File

@@ -1,11 +1,13 @@
package breaker package breaker
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
@@ -16,10 +18,274 @@ func init() {
} }
func TestCircuitBreaker_Allow(t *testing.T) { func TestCircuitBreaker_Allow(t *testing.T) {
b := NewBreaker() t.Run("allow", func(t *testing.T) {
assert.True(t, len(b.Name()) > 0) b := NewBreaker()
_, err := b.Allow() assert.True(t, len(b.Name()) > 0)
assert.Nil(t, err) _, err := b.Allow()
assert.Nil(t, err)
})
t.Run("allow with ctx", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
_, err := b.AllowCtx(context.Background())
assert.Nil(t, err)
})
t.Run("allow with ctx timeout", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
defer cancel()
time.Sleep(time.Millisecond)
_, err := b.AllowCtx(ctx)
assert.ErrorIs(t, err, context.DeadlineExceeded)
})
t.Run("allow with ctx cancel", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cancel()
_, err := b.AllowCtx(ctx)
assert.ErrorIs(t, err, context.Canceled)
}
_, err := b.AllowCtx(context.Background())
assert.NoError(t, err)
})
}
func TestCircuitBreaker_Do(t *testing.T) {
t.Run("do", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.Do(func() error {
return nil
})
assert.Nil(t, err)
})
t.Run("do with ctx", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoCtx(context.Background(), func() error {
return nil
})
assert.Nil(t, err)
})
t.Run("do with ctx timeout", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
defer cancel()
time.Sleep(time.Millisecond)
err := b.DoCtx(ctx, func() error {
return nil
})
assert.ErrorIs(t, err, context.DeadlineExceeded)
})
t.Run("do with ctx cancel", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cancel()
err := b.DoCtx(ctx, func() error {
return nil
})
assert.ErrorIs(t, err, context.Canceled)
}
assert.NoError(t, b.DoCtx(context.Background(), func() error {
return nil
}))
})
}
func TestCircuitBreaker_DoWithAcceptable(t *testing.T) {
t.Run("doWithAcceptable", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithAcceptable(func() error {
return nil
}, func(err error) bool {
return true
})
assert.Nil(t, err)
})
t.Run("doWithAcceptable with ctx", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithAcceptableCtx(context.Background(), func() error {
return nil
}, func(err error) bool {
return true
})
assert.Nil(t, err)
})
t.Run("doWithAcceptable with ctx timeout", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
defer cancel()
time.Sleep(time.Millisecond)
err := b.DoWithAcceptableCtx(ctx, func() error {
return nil
}, func(err error) bool {
return true
})
assert.ErrorIs(t, err, context.DeadlineExceeded)
})
t.Run("doWithAcceptable with ctx cancel", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cancel()
err := b.DoWithAcceptableCtx(ctx, func() error {
return nil
}, func(err error) bool {
return true
})
assert.ErrorIs(t, err, context.Canceled)
}
assert.NoError(t, b.DoWithAcceptableCtx(context.Background(), func() error {
return nil
}, func(err error) bool {
return true
}))
})
}
func TestCircuitBreaker_DoWithFallback(t *testing.T) {
t.Run("doWithFallback", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithFallback(func() error {
return nil
}, func(err error) error {
return err
})
assert.Nil(t, err)
})
t.Run("doWithFallback with ctx", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithFallbackCtx(context.Background(), func() error {
return nil
}, func(err error) error {
return err
})
assert.Nil(t, err)
})
t.Run("doWithFallback with ctx timeout", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
defer cancel()
time.Sleep(time.Millisecond)
err := b.DoWithFallbackCtx(ctx, func() error {
return nil
}, func(err error) error {
return err
})
assert.ErrorIs(t, err, context.DeadlineExceeded)
})
t.Run("doWithFallback with ctx cancel", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cancel()
err := b.DoWithFallbackCtx(ctx, func() error {
return nil
}, func(err error) error {
return err
})
assert.ErrorIs(t, err, context.Canceled)
}
assert.NoError(t, b.DoWithFallbackCtx(context.Background(), func() error {
return nil
}, func(err error) error {
return err
}))
})
}
func TestCircuitBreaker_DoWithFallbackAcceptable(t *testing.T) {
t.Run("doWithFallbackAcceptable", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithFallbackAcceptable(func() error {
return nil
}, func(err error) error {
return err
}, func(err error) bool {
return true
})
assert.Nil(t, err)
})
t.Run("doWithFallbackAcceptable with ctx", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
err := b.DoWithFallbackAcceptableCtx(context.Background(), func() error {
return nil
}, func(err error) error {
return err
}, func(err error) bool {
return true
})
assert.Nil(t, err)
})
t.Run("doWithFallbackAcceptable with ctx timeout", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
defer cancel()
time.Sleep(time.Millisecond)
err := b.DoWithFallbackAcceptableCtx(ctx, func() error {
return nil
}, func(err error) error {
return err
}, func(err error) bool {
return true
})
assert.ErrorIs(t, err, context.DeadlineExceeded)
})
t.Run("doWithFallbackAcceptable with ctx cancel", func(t *testing.T) {
b := NewBreaker()
assert.True(t, len(b.Name()) > 0)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cancel()
err := b.DoWithFallbackAcceptableCtx(ctx, func() error {
return nil
}, func(err error) error {
return err
}, func(err error) bool {
return true
})
assert.ErrorIs(t, err, context.Canceled)
}
assert.NoError(t, b.DoWithFallbackAcceptableCtx(context.Background(), func() error {
return nil
}, func(err error) error {
return err
}, func(err error) bool {
return true
}))
})
} }
func TestLogReason(t *testing.T) { func TestLogReason(t *testing.T) {

View File

@@ -1,6 +1,9 @@
package breaker package breaker
import "sync" import (
"context"
"sync"
)
var ( var (
lock sync.RWMutex lock sync.RWMutex
@@ -14,6 +17,13 @@ func Do(name string, req func() error) error {
}) })
} }
// DoCtx calls Breaker.DoCtx on the Breaker with given name.
func DoCtx(ctx context.Context, name string, req func() error) error {
return do(name, func(b Breaker) error {
return b.DoCtx(ctx, req)
})
}
// DoWithAcceptable calls Breaker.DoWithAcceptable on the Breaker with given name. // DoWithAcceptable calls Breaker.DoWithAcceptable on the Breaker with given name.
func DoWithAcceptable(name string, req func() error, acceptable Acceptable) error { func DoWithAcceptable(name string, req func() error, acceptable Acceptable) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
@@ -21,6 +31,14 @@ func DoWithAcceptable(name string, req func() error, acceptable Acceptable) erro
}) })
} }
// DoWithAcceptableCtx calls Breaker.DoWithAcceptableCtx on the Breaker with given name.
func DoWithAcceptableCtx(ctx context.Context, name string, req func() error,
acceptable Acceptable) error {
return do(name, func(b Breaker) error {
return b.DoWithAcceptableCtx(ctx, req, acceptable)
})
}
// DoWithFallback calls Breaker.DoWithFallback on the Breaker with given name. // DoWithFallback calls Breaker.DoWithFallback on the Breaker with given name.
func DoWithFallback(name string, req func() error, fallback Fallback) error { func DoWithFallback(name string, req func() error, fallback Fallback) error {
return do(name, func(b Breaker) error { return do(name, func(b Breaker) error {
@@ -28,6 +46,13 @@ func DoWithFallback(name string, req func() error, fallback Fallback) error {
}) })
} }
// DoWithFallbackCtx calls Breaker.DoWithFallbackCtx on the Breaker with given name.
func DoWithFallbackCtx(ctx context.Context, name string, req func() error, fallback Fallback) error {
return do(name, func(b Breaker) error {
return b.DoWithFallbackCtx(ctx, req, fallback)
})
}
// DoWithFallbackAcceptable calls Breaker.DoWithFallbackAcceptable on the Breaker with given name. // DoWithFallbackAcceptable calls Breaker.DoWithFallbackAcceptable on the Breaker with given name.
func DoWithFallbackAcceptable(name string, req func() error, fallback Fallback, func DoWithFallbackAcceptable(name string, req func() error, fallback Fallback,
acceptable Acceptable) error { acceptable Acceptable) error {
@@ -36,6 +61,14 @@ func DoWithFallbackAcceptable(name string, req func() error, fallback Fallback,
}) })
} }
// DoWithFallbackAcceptableCtx calls Breaker.DoWithFallbackAcceptableCtx on the Breaker with given name.
func DoWithFallbackAcceptableCtx(ctx context.Context, name string, req func() error,
fallback Fallback, acceptable Acceptable) error {
return do(name, func(b Breaker) error {
return b.DoWithFallbackAcceptableCtx(ctx, req, fallback, acceptable)
})
}
// GetBreaker returns the Breaker with the given name. // GetBreaker returns the Breaker with the given name.
func GetBreaker(name string) Breaker { func GetBreaker(name string) Breaker {
lock.RLock() lock.RLock()

View File

@@ -1,6 +1,7 @@
package breaker package breaker
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
@@ -22,6 +23,9 @@ func TestBreakersDo(t *testing.T) {
assert.Equal(t, errDummy, Do("any", func() error { assert.Equal(t, errDummy, Do("any", func() error {
return errDummy return errDummy
})) }))
assert.Equal(t, errDummy, DoCtx(context.Background(), "any", func() error {
return errDummy
}))
} }
func TestBreakersDoWithAcceptable(t *testing.T) { func TestBreakersDoWithAcceptable(t *testing.T) {
@@ -38,6 +42,13 @@ func TestBreakersDoWithAcceptable(t *testing.T) {
return nil return nil
}) == nil }) == nil
}) })
verify(t, func() bool {
return DoWithAcceptableCtx(context.Background(), "anyone", func() error {
return nil
}, func(err error) bool {
return true
}) == nil
})
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
err := DoWithAcceptable("another", func() error { err := DoWithAcceptable("another", func() error {
@@ -76,6 +87,12 @@ func TestBreakersFallback(t *testing.T) {
return nil return nil
}) })
assert.True(t, err == nil || errors.Is(err, errDummy)) assert.True(t, err == nil || errors.Is(err, errDummy))
err = DoWithFallbackCtx(context.Background(), "fallback", func() error {
return errDummy
}, func(err error) error {
return nil
})
assert.True(t, err == nil || errors.Is(err, errDummy))
} }
verify(t, func() bool { verify(t, func() bool {
return errors.Is(Do("fallback", func() error { return errors.Is(Do("fallback", func() error {
@@ -86,7 +103,7 @@ func TestBreakersFallback(t *testing.T) {
func TestBreakersAcceptableFallback(t *testing.T) { func TestBreakersAcceptableFallback(t *testing.T) {
errDummy := errors.New("any") errDummy := errors.New("any")
for i := 0; i < 10000; i++ { for i := 0; i < 5000; i++ {
err := DoWithFallbackAcceptable("acceptablefallback", func() error { err := DoWithFallbackAcceptable("acceptablefallback", func() error {
return errDummy return errDummy
}, func(err error) error { }, func(err error) error {
@@ -95,6 +112,14 @@ func TestBreakersAcceptableFallback(t *testing.T) {
return err == nil return err == nil
}) })
assert.True(t, err == nil || errors.Is(err, errDummy)) assert.True(t, err == nil || errors.Is(err, errDummy))
err = DoWithFallbackAcceptableCtx(context.Background(), "acceptablefallback", func() error {
return errDummy
}, func(err error) error {
return nil
}, func(err error) bool {
return err == nil
})
assert.True(t, err == nil || errors.Is(err, errDummy))
} }
verify(t, func() bool { verify(t, func() bool {
return errors.Is(Do("acceptablefallback", func() error { return errors.Is(Do("acceptablefallback", func() error {

48
core/breaker/bucket.go Normal file
View File

@@ -0,0 +1,48 @@
package breaker
const (
success = iota
fail
drop
)
// bucket defines the bucket that holds sum and num of additions.
type bucket struct {
Sum int64
Success int64
Failure int64
Drop int64
}
func (b *bucket) Add(v int64) {
switch v {
case fail:
b.fail()
case drop:
b.drop()
default:
b.succeed()
}
}
func (b *bucket) Reset() {
b.Sum = 0
b.Success = 0
b.Failure = 0
b.Drop = 0
}
func (b *bucket) drop() {
b.Sum++
b.Drop++
}
func (b *bucket) fail() {
b.Sum++
b.Failure++
}
func (b *bucket) succeed() {
b.Sum++
b.Success++
}

View File

@@ -0,0 +1,43 @@
package breaker
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBucketAdd(t *testing.T) {
b := &bucket{}
// Test succeed
b.Add(0) // Using 0 for success
assert.Equal(t, int64(1), b.Sum, "Sum should be incremented")
assert.Equal(t, int64(1), b.Success, "Success should be incremented")
assert.Equal(t, int64(0), b.Failure, "Failure should not be incremented")
assert.Equal(t, int64(0), b.Drop, "Drop should not be incremented")
// Test failure
b.Add(fail)
assert.Equal(t, int64(2), b.Sum, "Sum should be incremented")
assert.Equal(t, int64(1), b.Failure, "Failure should be incremented")
assert.Equal(t, int64(0), b.Drop, "Drop should not be incremented")
// Test drop
b.Add(drop)
assert.Equal(t, int64(3), b.Sum, "Sum should be incremented")
assert.Equal(t, int64(1), b.Drop, "Drop should be incremented")
}
func TestBucketReset(t *testing.T) {
b := &bucket{
Sum: 3,
Success: 1,
Failure: 1,
Drop: 1,
}
b.Reset()
assert.Equal(t, int64(0), b.Sum, "Sum should be reset to 0")
assert.Equal(t, int64(0), b.Success, "Success should be reset to 0")
assert.Equal(t, int64(0), b.Failure, "Failure should be reset to 0")
assert.Equal(t, int64(0), b.Drop, "Drop should be reset to 0")
}

View File

@@ -5,53 +5,95 @@ import (
"github.com/zeromicro/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/syncx"
"github.com/zeromicro/go-zero/core/timex"
) )
const ( const (
// 250ms for bucket duration // 250ms for bucket duration
window = time.Second * 10 window = time.Second * 10
buckets = 40 buckets = 40
k = 1.5 maxFailBucketsToDecreaseK = 30
protection = 5 minBucketsToSpeedUp = 3
forcePassDuration = time.Second
k = 1.5
minK = 1.1
recoveryK = 4 - k
protection = 5
) )
// googleBreaker is a netflixBreaker pattern from google. // googleBreaker is a netflixBreaker pattern from google.
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/ // see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
type googleBreaker struct { type (
k float64 googleBreaker struct {
stat *collection.RollingWindow k float64
proba *mathx.Proba stat *collection.RollingWindow[int64, *bucket]
} proba *mathx.Proba
lastPass *syncx.AtomicDuration
}
windowResult struct {
accepts int64
total int64
failingBuckets int64
workingBuckets int64
}
)
func newGoogleBreaker() *googleBreaker { func newGoogleBreaker() *googleBreaker {
bucketDuration := time.Duration(int64(window) / int64(buckets)) bucketDuration := time.Duration(int64(window) / int64(buckets))
st := collection.NewRollingWindow(buckets, bucketDuration) st := collection.NewRollingWindow[int64, *bucket](func() *bucket {
return new(bucket)
}, buckets, bucketDuration)
return &googleBreaker{ return &googleBreaker{
stat: st, stat: st,
k: k, k: k,
proba: mathx.NewProba(), proba: mathx.NewProba(),
lastPass: syncx.NewAtomicDuration(),
} }
} }
func (b *googleBreaker) accept() error { func (b *googleBreaker) accept() error {
accepts, total := b.history() var w float64
weightedAccepts := b.k * float64(accepts) history := b.history()
if history.failingBuckets >= minBucketsToSpeedUp {
w = b.k - float64(history.failingBuckets-1)*(b.k-minK)/maxFailBucketsToDecreaseK
w = mathx.AtLeast(w, minK)
} else {
w = b.k
}
weightedAccepts := w * float64(history.accepts)
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101 // https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
// for better performance, no need to care about negative ratio // for better performance, no need to care about the negative ratio
dropRatio := (float64(total-protection) - weightedAccepts) / float64(total+1) dropRatio := (float64(history.total-protection) - weightedAccepts) / float64(history.total+1)
if dropRatio <= 0 { if dropRatio <= 0 {
return nil return nil
} }
lastPass := b.lastPass.Load()
if lastPass > 0 && timex.Since(lastPass) > forcePassDuration {
b.lastPass.Set(timex.Now())
return nil
}
// If we have more than 2 working buckets, we are in recovery mode,
// the latest bucket is the current one, so we ignore it.
if history.workingBuckets >= minBucketsToSpeedUp {
dropRatio /= recoveryK
}
if b.proba.TrueOnProba(dropRatio) { if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable return ErrServiceUnavailable
} }
b.lastPass.Set(timex.Now())
return nil return nil
} }
func (b *googleBreaker) allow() (internalPromise, error) { func (b *googleBreaker) allow() (internalPromise, error) {
if err := b.accept(); err != nil { if err := b.accept(); err != nil {
b.markDrop()
return nil, err return nil, err
} }
@@ -62,7 +104,7 @@ func (b *googleBreaker) allow() (internalPromise, error) {
func (b *googleBreaker) doReq(req func() error, fallback Fallback, acceptable Acceptable) error { func (b *googleBreaker) doReq(req func() error, fallback Fallback, acceptable Acceptable) error {
if err := b.accept(); err != nil { if err := b.accept(); err != nil {
b.markFailure() b.markDrop()
if fallback != nil { if fallback != nil {
return fallback(err) return fallback(err)
} }
@@ -70,10 +112,10 @@ func (b *googleBreaker) doReq(req func() error, fallback Fallback, acceptable Ac
return err return err
} }
var success bool var succ bool
defer func() { defer func() {
// if req() panic, success is false, mark as failure // if req() panic, success is false, mark as failure
if success { if succ {
b.markSuccess() b.markSuccess()
} else { } else {
b.markFailure() b.markFailure()
@@ -82,27 +124,43 @@ func (b *googleBreaker) doReq(req func() error, fallback Fallback, acceptable Ac
err := req() err := req()
if acceptable(err) { if acceptable(err) {
success = true succ = true
} }
return err return err
} }
func (b *googleBreaker) markSuccess() { func (b *googleBreaker) markDrop() {
b.stat.Add(1) b.stat.Add(drop)
} }
func (b *googleBreaker) markFailure() { func (b *googleBreaker) markFailure() {
b.stat.Add(0) b.stat.Add(fail)
} }
func (b *googleBreaker) history() (accepts, total int64) { func (b *googleBreaker) markSuccess() {
b.stat.Reduce(func(b *collection.Bucket) { b.stat.Add(success)
accepts += int64(b.Sum) }
total += b.Count
func (b *googleBreaker) history() windowResult {
var result windowResult
b.stat.Reduce(func(b *bucket) {
result.accepts += b.Success
result.total += b.Sum
if b.Failure > 0 {
result.workingBuckets = 0
} else if b.Success > 0 {
result.workingBuckets++
}
if b.Success > 0 {
result.failingBuckets = 0
} else if b.Failure > 0 {
result.failingBuckets++
}
}) })
return return result
} }
type googlePromise struct { type googlePromise struct {

View File

@@ -10,6 +10,7 @@ import (
"github.com/zeromicro/go-zero/core/collection" "github.com/zeromicro/go-zero/core/collection"
"github.com/zeromicro/go-zero/core/mathx" "github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/stat" "github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/core/syncx"
) )
const ( const (
@@ -22,11 +23,14 @@ func init() {
} }
func getGoogleBreaker() *googleBreaker { func getGoogleBreaker() *googleBreaker {
st := collection.NewRollingWindow(testBuckets, testInterval) st := collection.NewRollingWindow[int64, *bucket](func() *bucket {
return new(bucket)
}, testBuckets, testInterval)
return &googleBreaker{ return &googleBreaker{
stat: st, stat: st,
k: 5, k: 5,
proba: mathx.NewProba(), proba: mathx.NewProba(),
lastPass: syncx.NewAtomicDuration(),
} }
} }
@@ -63,6 +67,33 @@ func TestGoogleBreakerOpen(t *testing.T) {
}) })
} }
func TestGoogleBreakerRecover(t *testing.T) {
st := collection.NewRollingWindow[int64, *bucket](func() *bucket {
return new(bucket)
}, testBuckets*2, testInterval)
b := &googleBreaker{
stat: st,
k: k,
proba: mathx.NewProba(),
lastPass: syncx.NewAtomicDuration(),
}
for i := 0; i < testBuckets; i++ {
for j := 0; j < 100; j++ {
b.stat.Add(1)
}
time.Sleep(testInterval)
}
for i := 0; i < testBuckets; i++ {
for j := 0; j < 100; j++ {
b.stat.Add(0)
}
time.Sleep(testInterval)
}
verify(t, func() bool {
return b.accept() == nil
})
}
func TestGoogleBreakerFallback(t *testing.T) { func TestGoogleBreakerFallback(t *testing.T) {
b := getGoogleBreaker() b := getGoogleBreaker()
markSuccess(b, 1) markSuccess(b, 1)
@@ -89,6 +120,43 @@ func TestGoogleBreakerReject(t *testing.T) {
}, nil, defaultAcceptable)) }, nil, defaultAcceptable))
} }
func TestGoogleBreakerMoreFallingBuckets(t *testing.T) {
t.Parallel()
t.Run("more falling buckets", func(t *testing.T) {
b := getGoogleBreaker()
func() {
stopChan := time.After(testInterval * minBucketsToSpeedUp * 2)
for {
time.Sleep(time.Millisecond)
select {
case <-stopChan:
return
default:
assert.Error(t, b.doReq(func() error {
return errors.New("foo")
}, func(err error) error {
return err
}, func(err error) bool {
return err == nil
}))
}
}
}()
var count int
for i := 0; i < 100; i++ {
if errors.Is(b.doReq(func() error {
return ErrServiceUnavailable
}, nil, defaultAcceptable), ErrServiceUnavailable) {
count++
}
}
assert.True(t, count > 90)
})
}
func TestGoogleBreakerAcceptable(t *testing.T) { func TestGoogleBreakerAcceptable(t *testing.T) {
b := getGoogleBreaker() b := getGoogleBreaker()
errAcceptable := errors.New("any") errAcceptable := errors.New("any")
@@ -164,41 +232,38 @@ func TestGoogleBreakerSelfProtection(t *testing.T) {
} }
func TestGoogleBreakerHistory(t *testing.T) { func TestGoogleBreakerHistory(t *testing.T) {
var b *googleBreaker
var accepts, total int64
sleep := testInterval sleep := testInterval
t.Run("accepts == total", func(t *testing.T) { t.Run("accepts == total", func(t *testing.T) {
b = getGoogleBreaker() b := getGoogleBreaker()
markSuccessWithDuration(b, 10, sleep/2) markSuccessWithDuration(b, 10, sleep/2)
accepts, total = b.history() result := b.history()
assert.Equal(t, int64(10), accepts) assert.Equal(t, int64(10), result.accepts)
assert.Equal(t, int64(10), total) assert.Equal(t, int64(10), result.total)
}) })
t.Run("fail == total", func(t *testing.T) { t.Run("fail == total", func(t *testing.T) {
b = getGoogleBreaker() b := getGoogleBreaker()
markFailedWithDuration(b, 10, sleep/2) markFailedWithDuration(b, 10, sleep/2)
accepts, total = b.history() result := b.history()
assert.Equal(t, int64(0), accepts) assert.Equal(t, int64(0), result.accepts)
assert.Equal(t, int64(10), total) assert.Equal(t, int64(10), result.total)
}) })
t.Run("accepts = 1/2 * total, fail = 1/2 * total", func(t *testing.T) { t.Run("accepts = 1/2 * total, fail = 1/2 * total", func(t *testing.T) {
b = getGoogleBreaker() b := getGoogleBreaker()
markFailedWithDuration(b, 5, sleep/2) markFailedWithDuration(b, 5, sleep/2)
markSuccessWithDuration(b, 5, sleep/2) markSuccessWithDuration(b, 5, sleep/2)
accepts, total = b.history() result := b.history()
assert.Equal(t, int64(5), accepts) assert.Equal(t, int64(5), result.accepts)
assert.Equal(t, int64(10), total) assert.Equal(t, int64(10), result.total)
}) })
t.Run("auto reset rolling counter", func(t *testing.T) { t.Run("auto reset rolling counter", func(t *testing.T) {
b = getGoogleBreaker() b := getGoogleBreaker()
time.Sleep(testInterval * testBuckets) time.Sleep(testInterval * testBuckets)
accepts, total = b.history() result := b.history()
assert.Equal(t, int64(0), accepts) assert.Equal(t, int64(0), result.accepts)
assert.Equal(t, int64(0), total) assert.Equal(t, int64(0), result.total)
}) })
} }

View File

@@ -1,5 +1,7 @@
package breaker package breaker
import "context"
const nopBreakerName = "nopBreaker" const nopBreakerName = "nopBreaker"
type nopBreaker struct{} type nopBreaker struct{}
@@ -17,22 +19,43 @@ func (b nopBreaker) Allow() (Promise, error) {
return nopPromise{}, nil return nopPromise{}, nil
} }
func (b nopBreaker) AllowCtx(_ context.Context) (Promise, error) {
return nopPromise{}, nil
}
func (b nopBreaker) Do(req func() error) error { func (b nopBreaker) Do(req func() error) error {
return req() return req()
} }
func (b nopBreaker) DoCtx(_ context.Context, req func() error) error {
return req()
}
func (b nopBreaker) DoWithAcceptable(req func() error, _ Acceptable) error { func (b nopBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
return req() return req()
} }
func (b nopBreaker) DoWithAcceptableCtx(_ context.Context, req func() error, _ Acceptable) error {
return req()
}
func (b nopBreaker) DoWithFallback(req func() error, _ Fallback) error { func (b nopBreaker) DoWithFallback(req func() error, _ Fallback) error {
return req() return req()
} }
func (b nopBreaker) DoWithFallbackCtx(_ context.Context, req func() error, _ Fallback) error {
return req()
}
func (b nopBreaker) DoWithFallbackAcceptable(req func() error, _ Fallback, _ Acceptable) error { func (b nopBreaker) DoWithFallbackAcceptable(req func() error, _ Fallback, _ Acceptable) error {
return req() return req()
} }
func (b nopBreaker) DoWithFallbackAcceptableCtx(_ context.Context, req func() error,
_ Fallback, _ Acceptable) error {
return req()
}
type nopPromise struct{} type nopPromise struct{}
func (p nopPromise) Accept() { func (p nopPromise) Accept() {

View File

@@ -1,6 +1,7 @@
package breaker package breaker
import ( import (
"context"
"errors" "errors"
"testing" "testing"
@@ -12,6 +13,8 @@ func TestNopBreaker(t *testing.T) {
assert.Equal(t, nopBreakerName, b.Name()) assert.Equal(t, nopBreakerName, b.Name())
p, err := b.Allow() p, err := b.Allow()
assert.Nil(t, err) assert.Nil(t, err)
p, err = b.AllowCtx(context.Background())
assert.Nil(t, err)
p.Accept() p.Accept()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
p, err := b.Allow() p, err := b.Allow()
@@ -21,18 +24,34 @@ func TestNopBreaker(t *testing.T) {
assert.Nil(t, b.Do(func() error { assert.Nil(t, b.Do(func() error {
return nil return nil
})) }))
assert.Nil(t, b.DoCtx(context.Background(), func() error {
return nil
}))
assert.Nil(t, b.DoWithAcceptable(func() error { assert.Nil(t, b.DoWithAcceptable(func() error {
return nil return nil
}, defaultAcceptable)) }, defaultAcceptable))
assert.Nil(t, b.DoWithAcceptableCtx(context.Background(), func() error {
return nil
}, defaultAcceptable))
errDummy := errors.New("any") errDummy := errors.New("any")
assert.Equal(t, errDummy, b.DoWithFallback(func() error { assert.Equal(t, errDummy, b.DoWithFallback(func() error {
return errDummy return errDummy
}, func(err error) error { }, func(err error) error {
return nil return nil
})) }))
assert.Equal(t, errDummy, b.DoWithFallbackCtx(context.Background(), func() error {
return errDummy
}, func(err error) error {
return nil
}))
assert.Equal(t, errDummy, b.DoWithFallbackAcceptable(func() error { assert.Equal(t, errDummy, b.DoWithFallbackAcceptable(func() error {
return errDummy return errDummy
}, func(err error) error { }, func(err error) error {
return nil return nil
}, defaultAcceptable)) }, defaultAcceptable))
assert.Equal(t, errDummy, b.DoWithFallbackAcceptableCtx(context.Background(), func() error {
return errDummy
}, func(err error) error {
return nil
}, defaultAcceptable))
} }

View File

@@ -23,7 +23,7 @@ var (
zero = big.NewInt(0) zero = big.NewInt(0)
) )
// DhKey defines the Diffie Hellman key. // DhKey defines the Diffie-Hellman key.
type DhKey struct { type DhKey struct {
PriKey *big.Int PriKey *big.Int
PubKey *big.Int PubKey *big.Int
@@ -46,7 +46,7 @@ func ComputeKey(pubKey, priKey *big.Int) (*big.Int, error) {
return new(big.Int).Exp(pubKey, priKey, p), nil return new(big.Int).Exp(pubKey, priKey, p), nil
} }
// GenerateKey returns a Diffie Hellman key. // GenerateKey returns a Diffie-Hellman key.
func GenerateKey() (*DhKey, error) { func GenerateKey() (*DhKey, error) {
var err error var err error
var x *big.Int var x *big.Int

View File

@@ -128,8 +128,8 @@ func (c *Cache) Take(key string, fetch func() (any, error)) (any, error) {
var fresh bool var fresh bool
val, err := c.barrier.Do(key, func() (any, error) { val, err := c.barrier.Do(key, func() (any, error) {
// because O(1) on map search in memory, and fetch is an IO query // because O(1) on map search in memory, and fetch is an IO query,
// so we do double check, cache might be taken by another call // so we do double-check, cache might be taken by another call
if val, ok := c.doGet(key); ok { if val, ok := c.doGet(key); ok {
return val, nil return val, nil
} }

View File

@@ -4,18 +4,28 @@ import (
"sync" "sync"
"time" "time"
"github.com/zeromicro/go-zero/core/mathx"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
) )
type ( type (
// RollingWindowOption let callers customize the RollingWindow. // BucketInterface is the interface that defines the buckets.
RollingWindowOption func(rollingWindow *RollingWindow) BucketInterface[T Numerical] interface {
Add(v T)
Reset()
}
// RollingWindow defines a rolling window to calculate the events in buckets with time interval. // Numerical is the interface that restricts the numerical type.
RollingWindow struct { Numerical = mathx.Numerical
// RollingWindowOption let callers customize the RollingWindow.
RollingWindowOption[T Numerical, B BucketInterface[T]] func(rollingWindow *RollingWindow[T, B])
// RollingWindow defines a rolling window to calculate the events in buckets with the time interval.
RollingWindow[T Numerical, B BucketInterface[T]] struct {
lock sync.RWMutex lock sync.RWMutex
size int size int
win *window win *window[T, B]
interval time.Duration interval time.Duration
offset int offset int
ignoreCurrent bool ignoreCurrent bool
@@ -25,14 +35,15 @@ type (
// NewRollingWindow returns a RollingWindow that with size buckets and time interval, // NewRollingWindow returns a RollingWindow that with size buckets and time interval,
// use opts to customize the RollingWindow. // use opts to customize the RollingWindow.
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow { func NewRollingWindow[T Numerical, B BucketInterface[T]](newBucket func() B, size int,
interval time.Duration, opts ...RollingWindowOption[T, B]) *RollingWindow[T, B] {
if size < 1 { if size < 1 {
panic("size must be greater than 0") panic("size must be greater than 0")
} }
w := &RollingWindow{ w := &RollingWindow[T, B]{
size: size, size: size,
win: newWindow(size), win: newWindow[T, B](newBucket, size),
interval: interval, interval: interval,
lastTime: timex.Now(), lastTime: timex.Now(),
} }
@@ -43,7 +54,7 @@ func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOpt
} }
// Add adds value to current bucket. // Add adds value to current bucket.
func (rw *RollingWindow) Add(v float64) { func (rw *RollingWindow[T, B]) Add(v T) {
rw.lock.Lock() rw.lock.Lock()
defer rw.lock.Unlock() defer rw.lock.Unlock()
rw.updateOffset() rw.updateOffset()
@@ -51,13 +62,13 @@ func (rw *RollingWindow) Add(v float64) {
} }
// Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set. // Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set.
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) { func (rw *RollingWindow[T, B]) Reduce(fn func(b B)) {
rw.lock.RLock() rw.lock.RLock()
defer rw.lock.RUnlock() defer rw.lock.RUnlock()
var diff int var diff int
span := rw.span() span := rw.span()
// ignore current bucket, because of partial data // ignore the current bucket, because of partial data
if span == 0 && rw.ignoreCurrent { if span == 0 && rw.ignoreCurrent {
diff = rw.size - 1 diff = rw.size - 1
} else { } else {
@@ -69,7 +80,7 @@ func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
} }
} }
func (rw *RollingWindow) span() int { func (rw *RollingWindow[T, B]) span() int {
offset := int(timex.Since(rw.lastTime) / rw.interval) offset := int(timex.Since(rw.lastTime) / rw.interval)
if 0 <= offset && offset < rw.size { if 0 <= offset && offset < rw.size {
return offset return offset
@@ -78,7 +89,7 @@ func (rw *RollingWindow) span() int {
return rw.size return rw.size
} }
func (rw *RollingWindow) updateOffset() { func (rw *RollingWindow[T, B]) updateOffset() {
span := rw.span() span := rw.span()
if span <= 0 { if span <= 0 {
return return
@@ -97,54 +108,54 @@ func (rw *RollingWindow) updateOffset() {
} }
// Bucket defines the bucket that holds sum and num of additions. // Bucket defines the bucket that holds sum and num of additions.
type Bucket struct { type Bucket[T Numerical] struct {
Sum float64 Sum T
Count int64 Count int64
} }
func (b *Bucket) add(v float64) { func (b *Bucket[T]) Add(v T) {
b.Sum += v b.Sum += v
b.Count++ b.Count++
} }
func (b *Bucket) reset() { func (b *Bucket[T]) Reset() {
b.Sum = 0 b.Sum = 0
b.Count = 0 b.Count = 0
} }
type window struct { type window[T Numerical, B BucketInterface[T]] struct {
buckets []*Bucket buckets []B
size int size int
} }
func newWindow(size int) *window { func newWindow[T Numerical, B BucketInterface[T]](newBucket func() B, size int) *window[T, B] {
buckets := make([]*Bucket, size) buckets := make([]B, size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
buckets[i] = new(Bucket) buckets[i] = newBucket()
} }
return &window{ return &window[T, B]{
buckets: buckets, buckets: buckets,
size: size, size: size,
} }
} }
func (w *window) add(offset int, v float64) { func (w *window[T, B]) add(offset int, v T) {
w.buckets[offset%w.size].add(v) w.buckets[offset%w.size].Add(v)
} }
func (w *window) reduce(start, count int, fn func(b *Bucket)) { func (w *window[T, B]) reduce(start, count int, fn func(b B)) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
fn(w.buckets[(start+i)%w.size]) fn(w.buckets[(start+i)%w.size])
} }
} }
func (w *window) resetBucket(offset int) { func (w *window[T, B]) resetBucket(offset int) {
w.buckets[offset%w.size].reset() w.buckets[offset%w.size].Reset()
} }
// IgnoreCurrentBucket lets the Reduce call ignore current bucket. // IgnoreCurrentBucket lets the Reduce call ignore current bucket.
func IgnoreCurrentBucket() RollingWindowOption { func IgnoreCurrentBucket[T Numerical, B BucketInterface[T]]() RollingWindowOption[T, B] {
return func(w *RollingWindow) { return func(w *RollingWindow[T, B]) {
w.ignoreCurrent = true w.ignoreCurrent = true
} }
} }

View File

@@ -12,18 +12,24 @@ import (
const duration = time.Millisecond * 50 const duration = time.Millisecond * 50
func TestNewRollingWindow(t *testing.T) { func TestNewRollingWindow(t *testing.T) {
assert.NotNil(t, NewRollingWindow(10, time.Second)) assert.NotNil(t, NewRollingWindow[int64, *Bucket[int64]](func() *Bucket[int64] {
return new(Bucket[int64])
}, 10, time.Second))
assert.Panics(t, func() { assert.Panics(t, func() {
NewRollingWindow(0, time.Second) NewRollingWindow[int64, *Bucket[int64]](func() *Bucket[int64] {
return new(Bucket[int64])
}, 0, time.Second)
}) })
} }
func TestRollingWindowAdd(t *testing.T) { func TestRollingWindowAdd(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration) r := NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, duration)
listBuckets := func() []float64 { listBuckets := func() []float64 {
var buckets []float64 var buckets []float64
r.Reduce(func(b *Bucket) { r.Reduce(func(b *Bucket[float64]) {
buckets = append(buckets, b.Sum) buckets = append(buckets, b.Sum)
}) })
return buckets return buckets
@@ -47,10 +53,12 @@ func TestRollingWindowAdd(t *testing.T) {
func TestRollingWindowReset(t *testing.T) { func TestRollingWindowReset(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration, IgnoreCurrentBucket()) r := NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, duration, IgnoreCurrentBucket[float64, *Bucket[float64]]())
listBuckets := func() []float64 { listBuckets := func() []float64 {
var buckets []float64 var buckets []float64
r.Reduce(func(b *Bucket) { r.Reduce(func(b *Bucket[float64]) {
buckets = append(buckets, b.Sum) buckets = append(buckets, b.Sum)
}) })
return buckets return buckets
@@ -72,15 +80,19 @@ func TestRollingWindowReset(t *testing.T) {
func TestRollingWindowReduce(t *testing.T) { func TestRollingWindowReduce(t *testing.T) {
const size = 4 const size = 4
tests := []struct { tests := []struct {
win *RollingWindow win *RollingWindow[float64, *Bucket[float64]]
expect float64 expect float64
}{ }{
{ {
win: NewRollingWindow(size, duration), win: NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, duration),
expect: 10, expect: 10,
}, },
{ {
win: NewRollingWindow(size, duration, IgnoreCurrentBucket()), win: NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, duration, IgnoreCurrentBucket[float64, *Bucket[float64]]()),
expect: 4, expect: 4,
}, },
} }
@@ -97,7 +109,7 @@ func TestRollingWindowReduce(t *testing.T) {
} }
} }
var result float64 var result float64
r.Reduce(func(b *Bucket) { r.Reduce(func(b *Bucket[float64]) {
result += b.Sum result += b.Sum
}) })
assert.Equal(t, test.expect, result) assert.Equal(t, test.expect, result)
@@ -108,10 +120,12 @@ func TestRollingWindowReduce(t *testing.T) {
func TestRollingWindowBucketTimeBoundary(t *testing.T) { func TestRollingWindowBucketTimeBoundary(t *testing.T) {
const size = 3 const size = 3
interval := time.Millisecond * 30 interval := time.Millisecond * 30
r := NewRollingWindow(size, interval) r := NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, interval)
listBuckets := func() []float64 { listBuckets := func() []float64 {
var buckets []float64 var buckets []float64
r.Reduce(func(b *Bucket) { r.Reduce(func(b *Bucket[float64]) {
buckets = append(buckets, b.Sum) buckets = append(buckets, b.Sum)
}) })
return buckets return buckets
@@ -138,7 +152,9 @@ func TestRollingWindowBucketTimeBoundary(t *testing.T) {
func TestRollingWindowDataRace(t *testing.T) { func TestRollingWindowDataRace(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration) r := NewRollingWindow[float64, *Bucket[float64]](func() *Bucket[float64] {
return new(Bucket[float64])
}, size, duration)
stop := make(chan bool) stop := make(chan bool)
go func() { go func() {
for { for {
@@ -157,7 +173,7 @@ func TestRollingWindowDataRace(t *testing.T) {
case <-stop: case <-stop:
return return
default: default:
r.Reduce(func(b *Bucket) {}) r.Reduce(func(b *Bucket[float64]) {})
} }
} }
}() }()

View File

@@ -133,7 +133,7 @@ func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName st
return newConflictKeyError(fullName) return newConflictKeyError(fullName)
} }
if err := mergeFields(prev, key, child.children, fullName); err != nil { if err := mergeFields(prev, child.children, fullName); err != nil {
return err return err
} }
} else { } else {
@@ -281,7 +281,7 @@ func getTagName(field reflect.StructField) string {
return field.Name return field.Name
} }
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo, fullName string) error { func mergeFields(prev *fieldInfo, children map[string]*fieldInfo, fullName string) error {
if len(prev.children) == 0 || len(children) == 0 { if len(prev.children) == 0 || len(children) == 0 {
return newConflictKeyError(fullName) return newConflictKeyError(fullName)
} }

View File

@@ -222,7 +222,7 @@ func (c *cluster) load(cli EtcdClient, key string) int64 {
break break
} }
logx.Error(err) logx.Errorf("%s, key is %s", err.Error(), key)
time.Sleep(coolDownInterval) time.Sleep(coolDownInterval)
} }

14
core/errorx/check.go Normal file
View File

@@ -0,0 +1,14 @@
package errorx
import "errors"
// In checks if the given err is one of errs.
func In(err error, errs ...error) bool {
for _, each := range errs {
if errors.Is(err, each) {
return true
}
}
return false
}

70
core/errorx/check_test.go Normal file
View File

@@ -0,0 +1,70 @@
package errorx
import (
"errors"
"testing"
)
func TestIn(t *testing.T) {
err1 := errors.New("error 1")
err2 := errors.New("error 2")
err3 := errors.New("error 3")
tests := []struct {
name string
err error
errs []error
want bool
}{
{
name: "Error matches one of the errors in the list",
err: err1,
errs: []error{err1, err2},
want: true,
},
{
name: "Error does not match any errors in the list",
err: err3,
errs: []error{err1, err2},
want: false,
},
{
name: "Empty error list",
err: err1,
errs: []error{},
want: false,
},
{
name: "Nil error with non-nil list",
err: nil,
errs: []error{err1, err2},
want: false,
},
{
name: "Non-nil error with nil in list",
err: err1,
errs: []error{nil, err2},
want: false,
},
{
name: "Error matches nil error in the list",
err: nil,
errs: []error{nil, err2},
want: true,
},
{
name: "Nil error with empty list",
err: nil,
errs: []error{},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := In(tt.err, tt.errs...); got != tt.want {
t.Errorf("In() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -5,7 +5,7 @@ import "gopkg.in/cheggaaa/pb.v1"
type ( type (
// A Scanner is used to read lines. // A Scanner is used to read lines.
Scanner interface { Scanner interface {
// Scan checks if has remaining to read. // Scan checks if it has remaining to read.
Scan() bool Scan() bool
// Text returns next line. // Text returns next line.
Text() string Text() string

View File

@@ -1,6 +1,9 @@
package fx package fx
import "github.com/zeromicro/go-zero/core/threading" import (
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/threading"
)
// Parallel runs fns parallelly and waits for done. // Parallel runs fns parallelly and waits for done.
func Parallel(fns ...func()) { func Parallel(fns ...func()) {
@@ -10,3 +13,20 @@ func Parallel(fns ...func()) {
} }
group.Wait() group.Wait()
} }
func ParallelErr(fns ...func() error) error {
var be errorx.BatchError
group := threading.NewRoutineGroup()
for _, fn := range fns {
f := fn
group.RunSafe(func() {
if err := f(); err != nil {
be.Add(err)
}
})
}
group.Wait()
return be.Err()
}

View File

@@ -1,6 +1,7 @@
package fx package fx
import ( import (
"errors"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
@@ -22,3 +23,54 @@ func TestParallel(t *testing.T) {
}) })
assert.Equal(t, int32(6), count) assert.Equal(t, int32(6), count)
} }
func TestParallelErr(t *testing.T) {
var count int32
err := ParallelErr(
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 1)
return errors.New("failed to exec #1")
},
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 2)
return errors.New("failed to exec #2")
},
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 3)
return nil
},
)
assert.Equal(t, int32(6), count)
assert.Error(t, err)
assert.ErrorContains(t, err, "failed to exec #1", "failed to exec #2")
}
func TestParallelErrErrorNil(t *testing.T) {
var count int32
err := ParallelErr(
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 1)
return nil
},
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 2)
return nil
},
func() error {
time.Sleep(time.Millisecond * 100)
atomic.AddInt32(&count, 3)
return nil
},
)
assert.Equal(t, int32(6), count)
assert.NoError(t, err)
}

View File

@@ -352,7 +352,7 @@ func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
}, opts...).Done() }, opts...).Done()
} }
// Reduce is an utility method to let the caller deal with the underlying channel. // Reduce is a utility method to let the caller deal with the underlying channel.
func (s Stream) Reduce(fn ReduceFunc) (any, error) { func (s Stream) Reduce(fn ReduceFunc) (any, error) {
return fn(s.source) return fn(s.source)
} }

View File

@@ -2,7 +2,7 @@ package iox
import "os" import "os"
// RedirectInOut redirects stdin to r, stdout to w, and callers need to call restore afterwards. // RedirectInOut redirects stdin to r, stdout to w, and callers need to call restore afterward.
func RedirectInOut() (restore func(), err error) { func RedirectInOut() (restore func(), err error) {
var r, w *os.File var r, w *os.File
r, w, err = os.Pipe() r, w, err = os.Pipe()

View File

@@ -9,7 +9,7 @@ import (
const bufSize = 32 * 1024 const bufSize = 32 * 1024
// CountLines returns the number of lines in file. // CountLines returns the number of lines in the file.
func CountLines(file string) (int, error) { func CountLines(file string) (int, error) {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {

View File

@@ -2,11 +2,12 @@ package iox
import ( import (
"bufio" "bufio"
"errors"
"io" "io"
"strings" "strings"
) )
// A TextLineScanner is a scanner that can scan lines from given reader. // A TextLineScanner is a scanner that can scan lines from the given reader.
type TextLineScanner struct { type TextLineScanner struct {
reader *bufio.Reader reader *bufio.Reader
hasNext bool hasNext bool
@@ -14,7 +15,7 @@ type TextLineScanner struct {
err error err error
} }
// NewTextLineScanner returns a TextLineScanner with given reader. // NewTextLineScanner returns a TextLineScanner with the given reader.
func NewTextLineScanner(reader io.Reader) *TextLineScanner { func NewTextLineScanner(reader io.Reader) *TextLineScanner {
return &TextLineScanner{ return &TextLineScanner{
reader: bufio.NewReader(reader), reader: bufio.NewReader(reader),
@@ -30,7 +31,7 @@ func (scanner *TextLineScanner) Scan() bool {
line, err := scanner.reader.ReadString('\n') line, err := scanner.reader.ReadString('\n')
scanner.line = strings.TrimRight(line, "\n") scanner.line = strings.TrimRight(line, "\n")
if err == io.EOF { if errors.Is(err, io.EOF) {
scanner.hasNext = false scanner.hasNext = false
return true return true
} else if err != nil { } else if err != nil {

View File

@@ -2,6 +2,7 @@ package limit
import ( import (
"context" "context"
_ "embed"
"errors" "errors"
"strconv" "strconv"
"time" "time"
@@ -28,20 +29,9 @@ var (
// ErrUnknownCode is an error that represents unknown status code. // ErrUnknownCode is an error that represents unknown status code.
ErrUnknownCode = errors.New("unknown status code") ErrUnknownCode = errors.New("unknown status code")
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key //go:embed periodscript.lua
periodScript = redis.NewScript(`local limit = tonumber(ARGV[1]) periodLuaScript string
local window = tonumber(ARGV[2]) periodScript = redis.NewScript(periodLuaScript)
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
end
if current < limit then
return 1
elseif current == limit then
return 2
else
return 0
end`)
) )
type ( type (

View File

@@ -0,0 +1,14 @@
-- to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call("INCRBY", KEYS[1], 1)
if current == 1 then
redis.call("expire", KEYS[1], window)
end
if current < limit then
return 1
elseif current == limit then
return 2
else
return 0
end

View File

@@ -2,6 +2,7 @@ package limit
import ( import (
"context" "context"
_ "embed"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
@@ -9,6 +10,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
xrate "golang.org/x/time/rate" xrate "golang.org/x/time/rate"
@@ -20,37 +22,11 @@ const (
pingInterval = time.Millisecond * 100 pingInterval = time.Millisecond * 100
) )
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key var (
// KEYS[1] as tokens_key //go:embed tokenscript.lua
// KEYS[2] as timestamp_key tokenLuaScript string
var script = redis.NewScript(`local rate = tonumber(ARGV[1]) tokenScript = redis.NewScript(tokenLuaScript)
local capacity = tonumber(ARGV[2]) )
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
local last_tokens = tonumber(redis.call("get", KEYS[1]))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - requested
end
redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)
return allowed`)
// A TokenLimiter controls how frequently events are allowed to happen with in one second. // A TokenLimiter controls how frequently events are allowed to happen with in one second.
type TokenLimiter struct { type TokenLimiter struct {
@@ -112,7 +88,7 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
} }
resp, err := lim.store.ScriptRunCtx(ctx, resp, err := lim.store.ScriptRunCtx(ctx,
script, tokenScript,
[]string{ []string{
lim.tokenKey, lim.tokenKey,
lim.timestampKey, lim.timestampKey,
@@ -125,10 +101,10 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
}) })
// redis allowed == false // redis allowed == false
// Lua boolean false -> r Nil bulk reply // Lua boolean false -> r Nil bulk reply
if err == redis.Nil { if errors.Is(err, redis.Nil) {
return false return false
} }
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { if errorx.In(err, context.DeadlineExceeded, context.Canceled) {
logx.Errorf("fail to use rate limiter: %s", err) logx.Errorf("fail to use rate limiter: %s", err)
return false return false
} }

View File

@@ -0,0 +1,31 @@
-- to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
-- KEYS[1] as tokens_key
-- KEYS[2] as timestamp_key
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
local last_tokens = tonumber(redis.call("get", KEYS[1]))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - requested
end
redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)
return allowed

View File

@@ -76,8 +76,8 @@ type (
avgFlyingLock syncx.SpinLock avgFlyingLock syncx.SpinLock
overloadTime *syncx.AtomicDuration overloadTime *syncx.AtomicDuration
droppedRecently *syncx.AtomicBool droppedRecently *syncx.AtomicBool
passCounter *collection.RollingWindow passCounter *collection.RollingWindow[int64, *collection.Bucket[int64]]
rtCounter *collection.RollingWindow rtCounter *collection.RollingWindow[int64, *collection.Bucket[int64]]
} }
) )
@@ -107,15 +107,16 @@ func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
opt(&options) opt(&options)
} }
bucketDuration := options.window / time.Duration(options.buckets) bucketDuration := options.window / time.Duration(options.buckets)
newBucket := func() *collection.Bucket[int64] {
return new(collection.Bucket[int64])
}
return &adaptiveShedder{ return &adaptiveShedder{
cpuThreshold: options.cpuThreshold, cpuThreshold: options.cpuThreshold,
windowScale: float64(time.Second) / float64(bucketDuration) / millisecondsPerSecond, windowScale: float64(time.Second) / float64(bucketDuration) / millisecondsPerSecond,
overloadTime: syncx.NewAtomicDuration(), overloadTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.NewAtomicBool(), droppedRecently: syncx.NewAtomicBool(),
passCounter: collection.NewRollingWindow(options.buckets, bucketDuration, passCounter: collection.NewRollingWindow[int64, *collection.Bucket[int64]](newBucket, options.buckets, bucketDuration, collection.IgnoreCurrentBucket[int64, *collection.Bucket[int64]]()),
collection.IgnoreCurrentBucket()), rtCounter: collection.NewRollingWindow[int64, *collection.Bucket[int64]](newBucket, options.buckets, bucketDuration, collection.IgnoreCurrentBucket[int64, *collection.Bucket[int64]]()),
rtCounter: collection.NewRollingWindow(options.buckets, bucketDuration,
collection.IgnoreCurrentBucket()),
} }
} }
@@ -138,10 +139,10 @@ func (as *adaptiveShedder) Allow() (Promise, error) {
func (as *adaptiveShedder) addFlying(delta int64) { func (as *adaptiveShedder) addFlying(delta int64) {
flying := atomic.AddInt64(&as.flying, delta) flying := atomic.AddInt64(&as.flying, delta)
// update avgFlying when the request is finished. // update avgFlying when the request is finished.
// this strategy makes avgFlying have a little bit lag against flying, and smoother. // this strategy makes avgFlying have a little bit of lag against flying, and smoother.
// when the flying requests increase rapidly, avgFlying increase slower, accept more requests. // when the flying requests increase rapidly, avgFlying increase slower, accept more requests.
// when the flying requests drop rapidly, avgFlying drop slower, accept fewer requests. // when the flying requests drop rapidly, avgFlying drop slower, accept fewer requests.
// it makes the service to serve as more requests as possible. // it makes the service to serve as many requests as possible.
if delta < 0 { if delta < 0 {
as.avgFlyingLock.Lock() as.avgFlyingLock.Lock()
as.avgFlying = as.avgFlying*flyingBeta + float64(flying)*(1-flyingBeta) as.avgFlying = as.avgFlying*flyingBeta + float64(flying)*(1-flyingBeta)
@@ -167,15 +168,15 @@ func (as *adaptiveShedder) maxFlight() float64 {
} }
func (as *adaptiveShedder) maxPass() int64 { func (as *adaptiveShedder) maxPass() int64 {
var result float64 = 1 var result int64 = 1
as.passCounter.Reduce(func(b *collection.Bucket) { as.passCounter.Reduce(func(b *collection.Bucket[int64]) {
if b.Sum > result { if b.Sum > result {
result = b.Sum result = b.Sum
} }
}) })
return int64(result) return result
} }
func (as *adaptiveShedder) minRt() float64 { func (as *adaptiveShedder) minRt() float64 {
@@ -183,12 +184,12 @@ func (as *adaptiveShedder) minRt() float64 {
// its a reasonable large value to avoid dropping requests. // its a reasonable large value to avoid dropping requests.
result := defaultMinRt result := defaultMinRt
as.rtCounter.Reduce(func(b *collection.Bucket) { as.rtCounter.Reduce(func(b *collection.Bucket[int64]) {
if b.Count <= 0 { if b.Count <= 0 {
return return
} }
avg := math.Round(b.Sum / float64(b.Count)) avg := math.Round(float64(b.Sum) / float64(b.Count))
if avg < result { if avg < result {
result = avg result = avg
} }
@@ -200,7 +201,7 @@ func (as *adaptiveShedder) minRt() float64 {
func (as *adaptiveShedder) overloadFactor() float64 { func (as *adaptiveShedder) overloadFactor() float64 {
// as.cpuThreshold must be less than cpuMax // as.cpuThreshold must be less than cpuMax
factor := (cpuMax - float64(stat.CpuUsage())) / (cpuMax - float64(as.cpuThreshold)) factor := (cpuMax - float64(stat.CpuUsage())) / (cpuMax - float64(as.cpuThreshold))
// at least accept 10% of acceptable requests even cpu is highly overloaded. // at least accept 10% of acceptable requests, even cpu is highly overloaded.
return mathx.Between(factor, overloadFactorLowerBound, 1) return mathx.Between(factor, overloadFactorLowerBound, 1)
} }
@@ -250,14 +251,14 @@ func (as *adaptiveShedder) systemOverloaded() bool {
return true return true
} }
// WithBuckets customizes the Shedder with given number of buckets. // WithBuckets customizes the Shedder with the given number of buckets.
func WithBuckets(buckets int) ShedderOption { func WithBuckets(buckets int) ShedderOption {
return func(opts *shedderOptions) { return func(opts *shedderOptions) {
opts.buckets = buckets opts.buckets = buckets
} }
} }
// WithCpuThreshold customizes the Shedder with given cpu threshold. // WithCpuThreshold customizes the Shedder with the given cpu threshold.
func WithCpuThreshold(threshold int64) ShedderOption { func WithCpuThreshold(threshold int64) ShedderOption {
return func(opts *shedderOptions) { return func(opts *shedderOptions) {
opts.cpuThreshold = threshold opts.cpuThreshold = threshold
@@ -283,6 +284,6 @@ func (p *promise) Fail() {
func (p *promise) Pass() { func (p *promise) Pass() {
rt := float64(timex.Since(p.start)) / float64(time.Millisecond) rt := float64(timex.Since(p.start)) / float64(time.Millisecond)
p.shedder.addFlying(-1) p.shedder.addFlying(-1)
p.shedder.rtCounter.Add(math.Ceil(rt)) p.shedder.rtCounter.Add(int64(math.Ceil(rt)))
p.shedder.passCounter.Add(1) p.shedder.passCounter.Add(1)
} }

View File

@@ -58,7 +58,7 @@ func TestAdaptiveShedder(t *testing.T) {
func TestAdaptiveShedderMaxPass(t *testing.T) { func TestAdaptiveShedderMaxPass(t *testing.T) {
passCounter := newRollingWindow() passCounter := newRollingWindow()
for i := 1; i <= 10; i++ { for i := 1; i <= 10; i++ {
passCounter.Add(float64(i * 100)) passCounter.Add(int64(i * 100))
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -83,7 +83,7 @@ func TestAdaptiveShedderMinRt(t *testing.T) {
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
for j := i*10 + 1; j <= i*10+10; j++ { for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j)) rtCounter.Add(int64(j))
} }
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -107,9 +107,9 @@ func TestAdaptiveShedderMaxFlight(t *testing.T) {
if i > 0 { if i > 0 {
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
passCounter.Add(float64((i + 1) * 100)) passCounter.Add(int64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ { for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j)) rtCounter.Add(int64(j))
} }
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -129,9 +129,9 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
if i > 0 { if i > 0 {
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
passCounter.Add(float64((i + 1) * 100)) passCounter.Add(int64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ { for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j)) rtCounter.Add(int64(j))
} }
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -184,9 +184,9 @@ func TestAdaptiveShedderStillHot(t *testing.T) {
if i > 0 { if i > 0 {
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
passCounter.Add(float64((i + 1) * 100)) passCounter.Add(int64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ { for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j)) rtCounter.Add(int64(j))
} }
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -248,9 +248,9 @@ func BenchmarkMaxFlight(b *testing.B) {
if i > 0 { if i > 0 {
time.Sleep(bucketDuration) time.Sleep(bucketDuration)
} }
passCounter.Add(float64((i + 1) * 100)) passCounter.Add(int64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ { for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j)) rtCounter.Add(int64(j))
} }
} }
shedder := &adaptiveShedder{ shedder := &adaptiveShedder{
@@ -265,6 +265,8 @@ func BenchmarkMaxFlight(b *testing.B) {
} }
} }
func newRollingWindow() *collection.RollingWindow { func newRollingWindow() *collection.RollingWindow[int64, *collection.Bucket[int64]] {
return collection.NewRollingWindow(buckets, bucketDuration, collection.IgnoreCurrentBucket()) return collection.NewRollingWindow[int64, *collection.Bucket[int64]](func() *collection.Bucket[int64] {
return new(collection.Bucket[int64])
}, buckets, bucketDuration, collection.IgnoreCurrentBucket[int64, *collection.Bucket[int64]]())
} }

View File

@@ -6,7 +6,7 @@ import (
"github.com/zeromicro/go-zero/core/syncx" "github.com/zeromicro/go-zero/core/syncx"
) )
// A ShedderGroup is a manager to manage key based shedders. // A ShedderGroup is a manager to manage key-based shedders.
type ShedderGroup struct { type ShedderGroup struct {
options []ShedderOption options []ShedderOption
manager *syncx.ResourceManager manager *syncx.ResourceManager

View File

@@ -42,7 +42,7 @@ func Debugv(ctx context.Context, v interface{}) {
getLogger(ctx).Debugv(v) getLogger(ctx).Debugv(v)
} }
// Debugw writes msg along with fields into access log. // Debugw writes msg along with fields into the access log.
func Debugw(ctx context.Context, msg string, fields ...LogField) { func Debugw(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Debugw(msg, fields...) getLogger(ctx).Debugw(msg, fields...)
} }
@@ -63,7 +63,7 @@ func Errorv(ctx context.Context, v any) {
getLogger(ctx).Errorv(v) getLogger(ctx).Errorv(v)
} }
// Errorw writes msg along with fields into error log. // Errorw writes msg along with fields into the error log.
func Errorw(ctx context.Context, msg string, fields ...LogField) { func Errorw(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Errorw(msg, fields...) getLogger(ctx).Errorw(msg, fields...)
} }
@@ -88,7 +88,7 @@ func Infov(ctx context.Context, v any) {
getLogger(ctx).Infov(v) getLogger(ctx).Infov(v)
} }
// Infow writes msg along with fields into access log. // Infow writes msg along with fields into the access log.
func Infow(ctx context.Context, msg string, fields ...LogField) { func Infow(ctx context.Context, msg string, fields ...LogField) {
getLogger(ctx).Infow(msg, fields...) getLogger(ctx).Infow(msg, fields...)
} }
@@ -108,10 +108,11 @@ func SetLevel(level uint32) {
logx.SetLevel(level) logx.SetLevel(level)
} }
// SetUp sets up the logx. If already set up, just return nil. // SetUp sets up the logx.
// we allow SetUp to be called multiple times, because for example // If already set up, return nil.
// We allow SetUp to be called multiple times, because, for example,
// we need to allow different service frameworks to initialize logx respectively. // we need to allow different service frameworks to initialize logx respectively.
// the same logic for SetUp // The same logic for SetUp
func SetUp(c LogConf) error { func SetUp(c LogConf) error {
return logx.SetUp(c) return logx.SetUp(c)
} }

View File

@@ -1,6 +1,6 @@
package logx package logx
// A LessLogger is a logger that control to log once during the given duration. // A LessLogger is a logger that controls to log once during the given duration.
type LessLogger struct { type LessLogger struct {
*limitedExecutor *limitedExecutor
} }

View File

@@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"path" "path"
"reflect"
"runtime/debug" "runtime/debug"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -86,7 +87,7 @@ func Debugv(v any) {
} }
} }
// Debugw writes msg along with fields into access log. // Debugw writes msg along with fields into the access log.
func Debugw(msg string, fields ...LogField) { func Debugw(msg string, fields ...LogField) {
if shallLog(DebugLevel) { if shallLog(DebugLevel) {
writeDebug(msg, fields...) writeDebug(msg, fields...)
@@ -142,7 +143,7 @@ func Errorv(v any) {
} }
} }
// Errorw writes msg along with fields into error log. // Errorw writes msg along with fields into the error log.
func Errorw(msg string, fields ...LogField) { func Errorw(msg string, fields ...LogField) {
if shallLog(ErrorLevel) { if shallLog(ErrorLevel) {
writeError(msg, fields...) writeError(msg, fields...)
@@ -153,11 +154,11 @@ func Errorw(msg string, fields ...LogField) {
func Field(key string, value any) LogField { func Field(key string, value any) LogField {
switch val := value.(type) { switch val := value.(type) {
case error: case error:
return LogField{Key: key, Value: val.Error()} return LogField{Key: key, Value: encodeError(val)}
case []error: case []error:
var errs []string var errs []string
for _, err := range val { for _, err := range val {
errs = append(errs, err.Error()) errs = append(errs, encodeError(err))
} }
return LogField{Key: key, Value: errs} return LogField{Key: key, Value: errs}
case time.Duration: case time.Duration:
@@ -175,11 +176,11 @@ func Field(key string, value any) LogField {
} }
return LogField{Key: key, Value: times} return LogField{Key: key, Value: times}
case fmt.Stringer: case fmt.Stringer:
return LogField{Key: key, Value: val.String()} return LogField{Key: key, Value: encodeStringer(val)}
case []fmt.Stringer: case []fmt.Stringer:
var strs []string var strs []string
for _, str := range val { for _, str := range val {
strs = append(strs, str.String()) strs = append(strs, encodeStringer(str))
} }
return LogField{Key: key, Value: strs} return LogField{Key: key, Value: strs}
default: default:
@@ -208,7 +209,7 @@ func Infov(v any) {
} }
} }
// Infow writes msg along with fields into access log. // Infow writes msg along with fields into the access log.
func Infow(msg string, fields ...LogField) { func Infow(msg string, fields ...LogField) {
if shallLog(InfoLevel) { if shallLog(InfoLevel) {
writeInfo(msg, fields...) writeInfo(msg, fields...)
@@ -254,11 +255,12 @@ func SetWriter(w Writer) {
} }
} }
// SetUp sets up the logx. If already set up, just return nil. // SetUp sets up the logx.
// we allow SetUp to be called multiple times, because for example // If already set up, return nil.
// We allow SetUp to be called multiple times, because, for example,
// we need to allow different service frameworks to initialize logx respectively. // we need to allow different service frameworks to initialize logx respectively.
func SetUp(c LogConf) (err error) { func SetUp(c LogConf) (err error) {
// Just ignore the subsequent SetUp calls. // Ignore the later SetUp calls.
// Because multiple services in one process might call SetUp respectively. // Because multiple services in one process might call SetUp respectively.
// Need to wait for the first caller to complete the execution. // Need to wait for the first caller to complete the execution.
setupOnce.Do(func() { setupOnce.Do(func() {
@@ -413,6 +415,32 @@ func createOutput(path string) (io.WriteCloser, error) {
return NewLogger(path, rule, options.gzipEnabled) return NewLogger(path, rule, options.gzipEnabled)
} }
func encodeError(err error) (ret string) {
return encodeWithRecover(err, func() string {
return err.Error()
})
}
func encodeStringer(v fmt.Stringer) (ret string) {
return encodeWithRecover(v, func() string {
return v.String()
})
}
func encodeWithRecover(arg any, fn func() string) (ret string) {
defer func() {
if err := recover(); err != nil {
if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() {
ret = nilAngleString
} else {
ret = fmt.Sprintf("panic: %v", err)
}
}
}()
return fn()
}
func getWriter() Writer { func getWriter() Writer {
w := writer.Load() w := writer.Load()
if w == nil { if w == nil {
@@ -480,7 +508,7 @@ func writeDebug(val any, fields ...LogField) {
getWriter().Debug(val, addCaller(fields...)...) getWriter().Debug(val, addCaller(fields...)...)
} }
// writeError writes v into error log. // writeError writes v into the error log.
// Not checking shallLog here is for performance consideration. // Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. // If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function. // The caller should check shallLog before calling this function.
@@ -520,7 +548,7 @@ func writeStack(msg string) {
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
} }
// writeStat writes v into stat log. // writeStat writes v into the stat log.
// Not checking shallLog here is for performance consideration. // Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. // If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function. // The caller should check shallLog before calling this function.

View File

@@ -348,6 +348,27 @@ func TestStructedLogInfow(t *testing.T) {
}) })
} }
func TestStructedLogFieldNil(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
assert.NotPanics(t, func() {
var s *string
Infow("test", Field("bb", s))
var d *nilStringer
Infow("test", Field("bb", d))
var e *nilError
Errorw("test", Field("bb", e))
})
assert.NotPanics(t, func() {
var p panicStringer
Infow("test", Field("bb", p))
var ps innerPanicStringer
Infow("test", Field("bb", ps))
})
}
func TestStructedLogInfoConsoleAny(t *testing.T) { func TestStructedLogInfoConsoleAny(t *testing.T) {
w := new(mockWriter) w := new(mockWriter)
old := writer.Swap(w) old := writer.Swap(w)
@@ -570,7 +591,7 @@ func TestErrorfWithWrappedError(t *testing.T) {
old := writer.Swap(w) old := writer.Swap(w)
defer writer.Store(old) defer writer.Store(old)
Errorf("hello %w", errors.New(message)) Errorf("hello %s", errors.New(message))
assert.True(t, strings.Contains(w.String(), "hello there")) assert.True(t, strings.Contains(w.String(), "hello there"))
} }
@@ -859,3 +880,36 @@ func validateFields(t *testing.T, content string, fields map[string]any) {
} }
} }
} }
type nilError struct {
Name string
}
func (e *nilError) Error() string {
return e.Name
}
type nilStringer struct {
Name string
}
func (s *nilStringer) String() string {
return s.Name
}
type innerPanicStringer struct {
Inner *struct {
Name string
}
}
func (s innerPanicStringer) String() string {
return s.Inner.Name
}
type panicStringer struct {
}
func (s panicStringer) String() string {
panic("panic")
}

View File

@@ -319,7 +319,7 @@ func (l *RotateLogger) maybeCompressFile(file string) {
}() }()
if _, err := os.Stat(file); err != nil { if _, err := os.Stat(file); err != nil {
// file not exists or other error, ignore compression // file doesn't exist or another error, ignore compression
return return
} }

View File

@@ -48,6 +48,7 @@ const (
levelDebug = "debug" levelDebug = "debug"
backupFileDelimiter = "-" backupFileDelimiter = "-"
nilAngleString = "<nil>"
flags = 0x0 flags = 0x0
) )

View File

@@ -254,11 +254,10 @@ func (n nopWriter) Stack(_ any) {
func (n nopWriter) Stat(_ any, _ ...LogField) { func (n nopWriter) Stat(_ any, _ ...LogField) {
} }
func buildPlainFields(fields ...LogField) []string { func buildPlainFields(fields logEntry) []string {
var items []string items := make([]string, 0, len(fields))
for k, v := range fields {
for _, field := range fields { items = append(items, fmt.Sprintf("%s=%+v", k, v))
items = append(items, fmt.Sprintf("%s=%+v", field.Key, field.Value))
} }
return items return items
@@ -278,6 +277,20 @@ func combineGlobalFields(fields []LogField) []LogField {
return ret return ret
} }
func marshalJson(t interface{}) ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(t)
// go 1.5+ will append a newline to the end of the json string
// https://github.com/golang/go/issues/13520
if l := buf.Len(); l > 0 && buf.Bytes()[l-1] == '\n' {
buf.Truncate(l - 1)
}
return buf.Bytes(), err
}
func output(writer io.Writer, level string, val any, fields ...LogField) { func output(writer io.Writer, level string, val any, fields ...LogField) {
// only truncate string content, don't know how to truncate the values of other types. // only truncate string content, don't know how to truncate the values of other types.
if v, ok := val.(string); ok { if v, ok := val.(string); ok {
@@ -289,15 +302,17 @@ func output(writer io.Writer, level string, val any, fields ...LogField) {
} }
fields = combineGlobalFields(fields) fields = combineGlobalFields(fields)
// +3 for timestamp, level and content
entry := make(logEntry, len(fields)+3)
for _, field := range fields {
entry[field.Key] = field.Value
}
switch atomic.LoadUint32(&encoding) { switch atomic.LoadUint32(&encoding) {
case plainEncodingType: case plainEncodingType:
writePlainAny(writer, level, val, buildPlainFields(fields...)...) plainFields := buildPlainFields(entry)
writePlainAny(writer, level, val, plainFields...)
default: default:
entry := make(logEntry)
for _, field := range fields {
entry[field.Key] = field.Value
}
entry[timestampKey] = getTimestamp() entry[timestampKey] = getTimestamp()
entry[levelKey] = level entry[levelKey] = level
entry[contentKey] = val entry[contentKey] = val
@@ -332,7 +347,7 @@ func wrapLevelWithColor(level string) string {
} }
func writeJson(writer io.Writer, info any) { func writeJson(writer io.Writer, info any) {
if content, err := json.Marshal(info); err != nil { if content, err := marshalJson(info); err != nil {
log.Printf("err: %s\n\n%s", err.Error(), debug.Stack()) log.Printf("err: %s\n\n%s", err.Error(), debug.Stack())
} else if writer == nil { } else if writer == nil {
log.Println(string(content)) log.Println(string(content))

View File

@@ -189,6 +189,41 @@ func TestWritePlainAny(t *testing.T) {
assert.Contains(t, buf.String(), "runtime/debug.Stack") assert.Contains(t, buf.String(), "runtime/debug.Stack")
} }
func TestWritePlainDuplicate(t *testing.T) {
old := atomic.SwapUint32(&encoding, plainEncodingType)
t.Cleanup(func() {
atomic.StoreUint32(&encoding, old)
})
var buf bytes.Buffer
output(&buf, levelInfo, "foo", LogField{
Key: "first",
Value: "a",
}, LogField{
Key: "first",
Value: "b",
})
assert.Contains(t, buf.String(), "foo")
assert.NotContains(t, buf.String(), "first=a")
assert.Contains(t, buf.String(), "first=b")
buf.Reset()
output(&buf, levelInfo, "foo", LogField{
Key: "first",
Value: "a",
}, LogField{
Key: "first",
Value: "b",
}, LogField{
Key: "second",
Value: "c",
})
assert.Contains(t, buf.String(), "foo")
assert.NotContains(t, buf.String(), "first=a")
assert.Contains(t, buf.String(), "first=b")
assert.Contains(t, buf.String(), "second=c")
}
func TestLogWithLimitContentLength(t *testing.T) { func TestLogWithLimitContentLength(t *testing.T) {
maxLen := atomic.LoadUint32(&maxContentLength) maxLen := atomic.LoadUint32(&maxContentLength)
atomic.StoreUint32(&maxContentLength, 10) atomic.StoreUint32(&maxContentLength, 10)

View File

@@ -12,7 +12,7 @@ const (
) )
// Marshal marshals the given val and returns the map that contains the fields. // Marshal marshals the given val and returns the map that contains the fields.
// optional=another is not implemented, and it's hard to implement and not common used. // optional=another is not implemented, and it's hard to implement and not commonly used.
func Marshal(val any) (map[string]map[string]any, error) { func Marshal(val any) (map[string]map[string]any, error) {
ret := make(map[string]map[string]any) ret := make(map[string]map[string]any)
tp := reflect.TypeOf(val) tp := reflect.TypeOf(val)

View File

@@ -39,7 +39,7 @@ var (
) )
type ( type (
// Unmarshaler is used to unmarshal with given tag key. // Unmarshaler is used to unmarshal with the given tag key.
Unmarshaler struct { Unmarshaler struct {
key string key string
opts unmarshalOptions opts unmarshalOptions
@@ -69,7 +69,7 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
return &unmarshaler return &unmarshaler
} }
// UnmarshalKey unmarshals m into v with tag key. // UnmarshalKey unmarshals m into v with the tag key.
func UnmarshalKey(m map[string]any, v any) error { func UnmarshalKey(m map[string]any, v any) error {
return keyUnmarshaler.Unmarshal(m, v) return keyUnmarshaler.Unmarshal(m, v)
} }
@@ -113,7 +113,8 @@ func (u *Unmarshaler) unmarshalValuer(m Valuer, v any, fullName string) error {
return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName) return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName)
} }
func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any, fullName string) error { func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value,
mapValue any, fullName string) error {
if !value.CanSet() { if !value.CanSet() {
return errValueNotSettable return errValueNotSettable
} }
@@ -154,7 +155,8 @@ func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue any) error
return nil return nil
} }
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any, fullName string) error { func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value,
mapValue any, fullName string) error {
if !value.CanSet() { if !value.CanSet() {
return errValueNotSettable return errValueNotSettable
} }
@@ -307,7 +309,34 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle
return u.fillSlice(derefedType, value, slice, fullName) return u.fillSlice(derefedType, value, slice, fullName)
} }
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any, fullName string) (reflect.Value, error) { func (u *Unmarshaler) fillUnmarshalerStruct(fieldType reflect.Type,
value reflect.Value, targetValue string) error {
if !value.CanSet() {
return errValueNotSettable
}
baseType := Deref(fieldType)
target := reflect.New(baseType)
switch u.key {
case jsonTagKey:
unmarshaler, ok := target.Interface().(json.Unmarshaler)
if !ok {
return errUnsupportedType
}
if err := unmarshaler.UnmarshalJSON([]byte(targetValue)); err != nil {
return err
}
default:
return errUnsupportedType
}
value.Set(target)
return nil
}
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any,
fullName string) (reflect.Value, error) {
mapType := reflect.MapOf(keyType, elemType) mapType := reflect.MapOf(keyType, elemType)
valueType := reflect.TypeOf(mapValue) valueType := reflect.TypeOf(mapValue)
if mapType == valueType { if mapType == valueType {
@@ -399,6 +428,15 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any,
return targetValue, nil return targetValue, nil
} }
func (u *Unmarshaler) implementsUnmarshaler(t reflect.Type) bool {
switch u.key {
case jsonTagKey:
return t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem())
default:
return false
}
}
func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Valuer, fullName string) ( func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Valuer, fullName string) (
string, *fieldOptionsWithContext, error) { string, *fieldOptionsWithContext, error) {
key, options, err := parseKeyAndOptions(u.key, field) key, options, err := parseKeyAndOptions(u.key, field)
@@ -576,6 +614,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
return u.fillSliceFromString(fieldType, value, mapValue, fullName) return u.fillSliceFromString(fieldType, value, mapValue, fullName)
case valueKind == reflect.String && derefedFieldType == durationType: case valueKind == reflect.String && derefedFieldType == durationType:
return fillDurationValue(fieldType, value, mapValue.(string)) return fillDurationValue(fieldType, value, mapValue.(string))
case valueKind == reflect.String && typeKind == reflect.Struct && u.implementsUnmarshaler(fieldType):
return u.fillUnmarshalerStruct(fieldType, value, mapValue.(string))
default: default:
return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName) return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName)
} }
@@ -629,7 +669,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
return err return err
} }
// if value is a pointer, we need to check overflow with the pointer's value. // if the value is a pointer, we need to check overflow with the pointer's value.
derefedValue := value derefedValue := value
for derefedValue.Type().Kind() == reflect.Ptr { for derefedValue.Type().Kind() == reflect.Ptr {
derefedValue = derefedValue.Elem() derefedValue = derefedValue.Elem()

View File

@@ -2,6 +2,7 @@ package mapping
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@@ -5760,6 +5761,49 @@ func TestUnmarshalWithIgnoreFields(t *testing.T) {
} }
} }
func TestUnmarshal_Unmarshaler(t *testing.T) {
t.Run("success", func(t *testing.T) {
v := struct {
Foo *mockUnmarshaler `json:"name"`
}{}
body := `{"name": "hello"}`
assert.NoError(t, UnmarshalJsonBytes([]byte(body), &v))
assert.Equal(t, "hello", v.Foo.Name)
})
t.Run("failure", func(t *testing.T) {
v := struct {
Foo *mockUnmarshalerWithError `json:"name"`
}{}
body := `{"name": "hello"}`
assert.Error(t, UnmarshalJsonBytes([]byte(body), &v))
})
t.Run("not json unmarshaler", func(t *testing.T) {
v := struct {
Foo *struct {
Name string
} `key:"name"`
}{}
u := NewUnmarshaler(defaultKeyName)
assert.Error(t, u.Unmarshal(map[string]any{
"name": "hello",
}, &v))
})
t.Run("not with json key", func(t *testing.T) {
v := struct {
Foo *mockUnmarshaler `json:"name"`
}{}
u := NewUnmarshaler(defaultKeyName)
// with different key, ignore
assert.NoError(t, u.Unmarshal(map[string]any{
"name": "hello",
}, &v))
assert.Nil(t, v.Foo)
})
}
func BenchmarkDefaultValue(b *testing.B) { func BenchmarkDefaultValue(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var a struct { var a struct {
@@ -5866,10 +5910,27 @@ type mockValuerWithParent struct {
ok bool ok bool
} }
func (m mockValuerWithParent) Value(key string) (any, bool) { func (m mockValuerWithParent) Value(_ string) (any, bool) {
return m.value, m.ok return m.value, m.ok
} }
func (m mockValuerWithParent) Parent() valuerWithParent { func (m mockValuerWithParent) Parent() valuerWithParent {
return m.parent return m.parent
} }
type mockUnmarshaler struct {
Name string
}
func (m *mockUnmarshaler) UnmarshalJSON(b []byte) error {
m.Name = string(b)
return nil
}
type mockUnmarshalerWithError struct {
Name string
}
func (m *mockUnmarshalerWithError) UnmarshalJSON(b []byte) error {
return errors.New("foo")
}

View File

@@ -416,7 +416,7 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
} }
// parseOptions parses the given options in tag. // parseOptions parses the given options in tag.
// for example: `json:"name,options=foo|bar"` or `json:"name,options=[foo,bar]"` // for example, `json:"name,options=foo|bar"` or `json:"name,options=[foo,bar]"`
func parseOptions(val string) []string { func parseOptions(val string) []string {
if len(val) == 0 { if len(val) == 0 {
return nil return nil

View File

@@ -26,9 +26,9 @@ type (
parent valuerWithParent parent valuerWithParent
} }
// mapValuer is a type for map to meet the Valuer interface. // mapValuer is a type for the map to meet the Valuer interface.
mapValuer map[string]any mapValuer map[string]any
// simpleValuer is a type to get value from current node. // simpleValuer is a type to get value from the current node.
simpleValuer node simpleValuer node
// recursiveValuer is a type to get the value recursively from current and parent nodes. // recursiveValuer is a type to get the value recursively from current and parent nodes.
recursiveValuer node recursiveValuer node

View File

@@ -1,13 +1,13 @@
package mathx package mathx
type numerical interface { type Numerical interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64 ~float32 | ~float64
} }
// AtLeast returns the greater of x or lower. // AtLeast returns the greater of x or lower.
func AtLeast[T numerical](x, lower T) T { func AtLeast[T Numerical](x, lower T) T {
if x < lower { if x < lower {
return lower return lower
} }
@@ -15,7 +15,7 @@ func AtLeast[T numerical](x, lower T) T {
} }
// AtMost returns the smaller of x or upper. // AtMost returns the smaller of x or upper.
func AtMost[T numerical](x, upper T) T { func AtMost[T Numerical](x, upper T) T {
if x > upper { if x > upper {
return upper return upper
} }
@@ -23,7 +23,7 @@ func AtMost[T numerical](x, upper T) T {
} }
// Between returns the value of x clamped to the range [lower, upper]. // Between returns the value of x clamped to the range [lower, upper].
func Between[T numerical](x, lower, upper T) T { func Between[T Numerical](x, lower, upper T) T {
if x < lower { if x < lower {
return lower return lower
} }

View File

@@ -36,6 +36,6 @@ type fakeCreator struct {
err error err error
} }
func (fc fakeCreator) Create(name string) (file *os.File, err error) { func (fc fakeCreator) Create(_ string) (file *os.File, err error) {
return fc.file, fc.err return fc.file, fc.err
} }

View File

@@ -76,7 +76,7 @@ func (q *Queue) AddListener(listener Listener) {
q.listeners = append(q.listeners, listener) q.listeners = append(q.listeners, listener)
} }
// Broadcast broadcasts message to all event channels. // Broadcast broadcasts the message to all event channels.
func (q *Queue) Broadcast(message any) { func (q *Queue) Broadcast(message any) {
go func() { go func() {
q.eventLock.Lock() q.eventLock.Lock()
@@ -202,7 +202,7 @@ func (q *Queue) produce() {
} }
func (q *Queue) produceOne(producer Producer) (string, bool) { func (q *Queue) produceOne(producer Producer) (string, bool) {
// avoid panic quit the producer, just log it and continue // avoid panic quit the producer, log it and continue
defer rescue.Recover() defer rescue.Recover()
return producer.Produce() return producer.Produce()

View File

@@ -67,7 +67,7 @@ func (p *mockedPusher) Name() string {
return p.name return p.name
} }
func (p *mockedPusher) Push(s string) error { func (p *mockedPusher) Push(_ string) error {
if proba.TrueOnProba(failProba) { if proba.TrueOnProba(failProba) {
return errors.New("dummy") return errors.New("dummy")
} }

View File

@@ -71,6 +71,6 @@ func (m *mockedWriter) Write(report *StatReport) error {
type badWriter struct{} type badWriter struct{}
func (b *badWriter) Write(report *StatReport) error { func (b *badWriter) Write(_ *StatReport) error {
return errors.New("bad") return errors.New("bad")
} }

View File

@@ -1,6 +1,7 @@
package stat package stat
import ( import (
"errors"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -28,3 +29,14 @@ func TestRemoteWriterFail(t *testing.T) {
}) })
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestRemoteWriterError(t *testing.T) {
defer gock.Off()
gock.New("http://foo.com").ReplyError(errors.New("foo"))
writer := NewRemoteWriter("http://foo.com")
err := writer.Write(&StatReport{
Name: "bar",
})
assert.NotNil(t, err)
}

View File

@@ -2,7 +2,7 @@ package stat
import "time" import "time"
// A Task is a task that is reported to Metrics. // A Task is a task reported to Metrics.
type Task struct { type Task struct {
Drop bool Drop bool
Duration time.Duration Duration time.Duration

View File

@@ -41,7 +41,7 @@ func RawFieldNames(in any, postgreSql ...bool) []string {
out = append(out, fmt.Sprintf("`%s`", fi.Name)) out = append(out, fmt.Sprintf("`%s`", fi.Name))
} }
default: default:
// get tag name with the tag opton, e.g.: // get tag name with the tag option, e.g.:
// `db:"id"` // `db:"id"`
// `db:"id,type=char,length=16"` // `db:"id,type=char,length=16"`
// `db:",type=char,length=16"` // `db:",type=char,length=16"`

View File

@@ -8,7 +8,7 @@ const (
) )
type ( type (
// An Options is used to store the cache options. // Options is used to store the cache options.
Options struct { Options struct {
Expiry time.Duration Expiry time.Duration
NotFoundExpiry time.Duration NotFoundExpiry time.Duration

View File

@@ -2,10 +2,10 @@ package mon
import ( import (
"context" "context"
"errors"
"time" "time"
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/timex" "github.com/zeromicro/go-zero/core/timex"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
mopt "go.mongodb.org/mongo-driver/mongo/options" mopt "go.mongodb.org/mongo-driver/mongo/options"
@@ -141,7 +141,7 @@ func (c *decoratedCollection) Aggregate(ctx context.Context, pipeline any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, aggregate, starTime, err) c.logDurationSimple(ctx, aggregate, starTime, err)
@@ -161,7 +161,7 @@ func (c *decoratedCollection) BulkWrite(ctx context.Context, models []mongo.Writ
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, bulkWrite, startTime, err) c.logDurationSimple(ctx, bulkWrite, startTime, err)
@@ -181,7 +181,7 @@ func (c *decoratedCollection) CountDocuments(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, countDocuments, startTime, err) c.logDurationSimple(ctx, countDocuments, startTime, err)
@@ -201,7 +201,7 @@ func (c *decoratedCollection) DeleteMany(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, deleteMany, startTime, err) c.logDurationSimple(ctx, deleteMany, startTime, err)
@@ -221,7 +221,7 @@ func (c *decoratedCollection) DeleteOne(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, deleteOne, startTime, err, filter) c.logDuration(ctx, deleteOne, startTime, err, filter)
@@ -241,7 +241,7 @@ func (c *decoratedCollection) Distinct(ctx context.Context, fieldName string, fi
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, distinct, startTime, err) c.logDurationSimple(ctx, distinct, startTime, err)
@@ -261,7 +261,7 @@ func (c *decoratedCollection) EstimatedDocumentCount(ctx context.Context,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, estimatedDocumentCount, startTime, err) c.logDurationSimple(ctx, estimatedDocumentCount, startTime, err)
@@ -281,7 +281,7 @@ func (c *decoratedCollection) Find(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, find, startTime, err, filter) c.logDuration(ctx, find, startTime, err, filter)
@@ -301,7 +301,7 @@ func (c *decoratedCollection) FindOne(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, findOne, startTime, err, filter) c.logDuration(ctx, findOne, startTime, err, filter)
@@ -322,7 +322,7 @@ func (c *decoratedCollection) FindOneAndDelete(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, findOneAndDelete, startTime, err, filter) c.logDuration(ctx, findOneAndDelete, startTime, err, filter)
@@ -344,7 +344,7 @@ func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, findOneAndReplace, startTime, err, filter, replacement) c.logDuration(ctx, findOneAndReplace, startTime, err, filter, replacement)
@@ -365,7 +365,7 @@ func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter, upda
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, findOneAndUpdate, startTime, err, filter, update) c.logDuration(ctx, findOneAndUpdate, startTime, err, filter, update)
@@ -386,7 +386,7 @@ func (c *decoratedCollection) InsertMany(ctx context.Context, documents []any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, insertMany, startTime, err) c.logDurationSimple(ctx, insertMany, startTime, err)
@@ -406,7 +406,7 @@ func (c *decoratedCollection) InsertOne(ctx context.Context, document any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, insertOne, startTime, err, document) c.logDuration(ctx, insertOne, startTime, err, document)
@@ -426,7 +426,7 @@ func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter, replacemen
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, replaceOne, startTime, err, filter, replacement) c.logDuration(ctx, replaceOne, startTime, err, filter, replacement)
@@ -446,7 +446,7 @@ func (c *decoratedCollection) UpdateByID(ctx context.Context, id, update any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, updateByID, startTime, err, id, update) c.logDuration(ctx, updateByID, startTime, err, id, update)
@@ -466,7 +466,7 @@ func (c *decoratedCollection) UpdateMany(ctx context.Context, filter, update any
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDurationSimple(ctx, updateMany, startTime, err) c.logDurationSimple(ctx, updateMany, startTime, err)
@@ -486,7 +486,7 @@ func (c *decoratedCollection) UpdateOne(ctx context.Context, filter, update any,
endSpan(span, err) endSpan(span, err)
}() }()
err = c.brk.DoWithAcceptable(func() error { err = c.brk.DoWithAcceptableCtx(ctx, func() error {
startTime := timex.Now() startTime := timex.Now()
defer func() { defer func() {
c.logDuration(ctx, updateOne, startTime, err, filter, update) c.logDuration(ctx, updateOne, startTime, err, filter, update)
@@ -527,19 +527,10 @@ func (p keepablePromise) keep(err error) error {
} }
func acceptable(err error) bool { func acceptable(err error) bool {
return err == nil || return err == nil || errorx.In(err, mongo.ErrNoDocuments, mongo.ErrNilValue,
errors.Is(err, mongo.ErrNoDocuments) || mongo.ErrNilDocument, mongo.ErrNilCursor, mongo.ErrEmptySlice,
errors.Is(err, mongo.ErrNilValue) ||
errors.Is(err, mongo.ErrNilDocument) ||
errors.Is(err, mongo.ErrNilCursor) ||
errors.Is(err, mongo.ErrEmptySlice) ||
// session errors // session errors
errors.Is(err, session.ErrSessionEnded) || session.ErrSessionEnded, session.ErrNoTransactStarted, session.ErrTransactInProgress,
errors.Is(err, session.ErrNoTransactStarted) || session.ErrAbortAfterCommit, session.ErrAbortTwice, session.ErrCommitAfterAbort,
errors.Is(err, session.ErrTransactInProgress) || session.ErrUnackWCUnsupported, session.ErrSnapshotTransaction)
errors.Is(err, session.ErrAbortAfterCommit) ||
errors.Is(err, session.ErrAbortTwice) ||
errors.Is(err, session.ErrCommitAfterAbort) ||
errors.Is(err, session.ErrUnackWCUnsupported) ||
errors.Is(err, session.ErrSnapshotTransaction)
} }

View File

@@ -595,19 +595,40 @@ func (d *dropBreaker) Allow() (breaker.Promise, error) {
return nil, errDummy return nil, errDummy
} }
func (d *dropBreaker) AllowCtx(_ context.Context) (breaker.Promise, error) {
return nil, errDummy
}
func (d *dropBreaker) Do(_ func() error) error { func (d *dropBreaker) Do(_ func() error) error {
return nil return nil
} }
func (d *dropBreaker) DoCtx(_ context.Context, _ func() error) error {
return nil
}
func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) error { func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) error {
return errDummy return errDummy
} }
func (d *dropBreaker) DoWithAcceptableCtx(_ context.Context, _ func() error, _ breaker.Acceptable) error {
return errDummy
}
func (d *dropBreaker) DoWithFallback(_ func() error, _ breaker.Fallback) error { func (d *dropBreaker) DoWithFallback(_ func() error, _ breaker.Fallback) error {
return nil return nil
} }
func (d *dropBreaker) DoWithFallbackCtx(_ context.Context, _ func() error, _ breaker.Fallback) error {
return nil
}
func (d *dropBreaker) DoWithFallbackAcceptable(_ func() error, _ breaker.Fallback, func (d *dropBreaker) DoWithFallbackAcceptable(_ func() error, _ breaker.Fallback,
_ breaker.Acceptable) error { _ breaker.Acceptable) error {
return nil return nil
} }
func (d *dropBreaker) DoWithFallbackAcceptableCtx(_ context.Context, _ func() error,
_ breaker.Fallback, _ breaker.Acceptable) error {
return nil
}

View File

@@ -69,27 +69,21 @@ func newModel(name string, cli *mongo.Client, coll Collection, brk breaker.Break
// StartSession starts a new session. // StartSession starts a new session.
func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session, err error) { func (m *Model) StartSession(opts ...*mopt.SessionOptions) (sess mongo.Session, err error) {
err = m.brk.DoWithAcceptable(func() error { starTime := timex.Now()
starTime := timex.Now() defer func() {
defer func() { logDuration(context.Background(), m.name, startSession, starTime, err)
logDuration(context.Background(), m.name, startSession, starTime, err) }()
}()
session, sessionErr := m.cli.StartSession(opts...) session, sessionErr := m.cli.StartSession(opts...)
if sessionErr != nil { if sessionErr != nil {
return sessionErr return nil, sessionErr
} }
sess = &wrappedSession{ return &wrappedSession{
Session: session, Session: session,
name: m.name, name: m.name,
brk: m.brk, brk: m.brk,
} }, nil
return nil
}, acceptable)
return
} }
// Aggregate executes an aggregation pipeline. // Aggregate executes an aggregation pipeline.
@@ -184,7 +178,7 @@ func (w *wrappedSession) AbortTransaction(ctx context.Context) (err error) {
endSpan(span, err) endSpan(span, err)
}() }()
return w.brk.DoWithAcceptable(func() error { return w.brk.DoWithAcceptableCtx(ctx, func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
logDuration(ctx, w.name, abortTransaction, starTime, err) logDuration(ctx, w.name, abortTransaction, starTime, err)
@@ -201,7 +195,7 @@ func (w *wrappedSession) CommitTransaction(ctx context.Context) (err error) {
endSpan(span, err) endSpan(span, err)
}() }()
return w.brk.DoWithAcceptable(func() error { return w.brk.DoWithAcceptableCtx(ctx, func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
logDuration(ctx, w.name, commitTransaction, starTime, err) logDuration(ctx, w.name, commitTransaction, starTime, err)
@@ -222,7 +216,7 @@ func (w *wrappedSession) WithTransaction(
endSpan(span, err) endSpan(span, err)
}() }()
err = w.brk.DoWithAcceptable(func() error { err = w.brk.DoWithAcceptableCtx(ctx, func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
logDuration(ctx, w.name, withTransaction, starTime, err) logDuration(ctx, w.name, withTransaction, starTime, err)
@@ -243,7 +237,7 @@ func (w *wrappedSession) EndSession(ctx context.Context) {
endSpan(span, err) endSpan(span, err)
}() }()
err = w.brk.DoWithAcceptable(func() error { err = w.brk.DoWithAcceptableCtx(ctx, func() error {
starTime := timex.Now() starTime := timex.Now()
defer func() { defer func() {
logDuration(ctx, w.name, endSession, starTime, err) logDuration(ctx, w.name, endSession, starTime, err)

View File

@@ -2,8 +2,8 @@ package mon
import ( import (
"context" "context"
"errors"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/trace" "github.com/zeromicro/go-zero/core/trace"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
@@ -24,8 +24,7 @@ func startSpan(ctx context.Context, cmd string) (context.Context, oteltrace.Span
func endSpan(span oteltrace.Span, err error) { func endSpan(span oteltrace.Span, err error) {
defer span.End() defer span.End()
if err == nil || errors.Is(err, mongo.ErrNoDocuments) || if err == nil || errorx.In(err, mongo.ErrNoDocuments, mongo.ErrNilValue, mongo.ErrNilDocument) {
errors.Is(err, mongo.ErrNilValue) || errors.Is(err, mongo.ErrNilDocument) {
span.SetStatus(codes.Ok, "") span.SetStatus(codes.Ok, "")
return return
} }

View File

@@ -0,0 +1,41 @@
package redis
import (
"context"
red "github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/lang"
)
var ignoreCmds = map[string]lang.PlaceholderType{
"blpop": {},
}
type breakerHook struct {
brk breaker.Breaker
}
func (h breakerHook) DialHook(next red.DialHook) red.DialHook {
return next
}
func (h breakerHook) ProcessHook(next red.ProcessHook) red.ProcessHook {
return func(ctx context.Context, cmd red.Cmder) error {
if _, ok := ignoreCmds[cmd.Name()]; ok {
return next(ctx, cmd)
}
return h.brk.DoWithAcceptableCtx(ctx, func() error {
return next(ctx, cmd)
}, acceptable)
}
}
func (h breakerHook) ProcessPipelineHook(next red.ProcessPipelineHook) red.ProcessPipelineHook {
return func(ctx context.Context, cmds []red.Cmder) error {
return h.brk.DoWithAcceptableCtx(ctx, func() error {
return next(ctx, cmds)
}, acceptable)
}
}

View File

@@ -0,0 +1,135 @@
package redis
import (
"context"
"errors"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/breaker"
)
func TestBreakerHook_ProcessHook(t *testing.T) {
t.Run("breakerHookOpen", func(t *testing.T) {
s := miniredis.RunT(t)
rds := MustNewRedis(RedisConf{
Host: s.Addr(),
Type: NodeType,
})
someError := errors.New("ERR some error")
s.SetError(someError.Error())
var err error
for i := 0; i < 1000; i++ {
_, err = rds.Get("key")
if err != nil && err.Error() != someError.Error() {
break
}
}
assert.Equal(t, breaker.ErrServiceUnavailable, err)
})
t.Run("breakerHookClose", func(t *testing.T) {
s := miniredis.RunT(t)
rds := MustNewRedis(RedisConf{
Host: s.Addr(),
Type: NodeType,
})
var err error
for i := 0; i < 1000; i++ {
_, err = rds.Get("key")
if err != nil {
break
}
}
assert.NotEqual(t, breaker.ErrServiceUnavailable, err)
})
t.Run("breakerHook_ignoreCmd", func(t *testing.T) {
s := miniredis.RunT(t)
rds := MustNewRedis(RedisConf{
Host: s.Addr(),
Type: NodeType,
})
someError := errors.New("ERR some error")
s.SetError(someError.Error())
var err error
node, err := getRedis(rds)
assert.NoError(t, err)
for i := 0; i < 1000; i++ {
_, err = rds.Blpop(node, "key")
if err != nil && err.Error() != someError.Error() {
break
}
}
assert.Equal(t, someError.Error(), err.Error())
})
}
func TestBreakerHook_ProcessPipelineHook(t *testing.T) {
t.Run("breakerPipelineHookOpen", func(t *testing.T) {
s := miniredis.RunT(t)
rds := MustNewRedis(RedisConf{
Host: s.Addr(),
Type: NodeType,
})
someError := errors.New("ERR some error")
s.SetError(someError.Error())
var err error
for i := 0; i < 1000; i++ {
err = rds.Pipelined(
func(pipe Pipeliner) error {
pipe.Incr(context.Background(), "pipelined_counter")
pipe.Expire(context.Background(), "pipelined_counter", time.Hour)
pipe.ZAdd(context.Background(), "zadd", Z{Score: 12, Member: "zadd"})
return nil
},
)
if err != nil && err.Error() != someError.Error() {
break
}
}
assert.Equal(t, breaker.ErrServiceUnavailable, err)
})
t.Run("breakerPipelineHookClose", func(t *testing.T) {
s := miniredis.RunT(t)
rds := MustNewRedis(RedisConf{
Host: s.Addr(),
Type: NodeType,
})
var err error
for i := 0; i < 1000; i++ {
err = rds.Pipelined(
func(pipe Pipeliner) error {
pipe.Incr(context.Background(), "pipelined_counter")
pipe.Expire(context.Background(), "pipelined_counter", time.Hour)
pipe.ZAdd(context.Background(), "zadd", Z{Score: 12, Member: "zadd"})
return nil
},
)
if err != nil {
break
}
}
assert.NotEqual(t, breaker.ErrServiceUnavailable, err)
})
}

View File

@@ -47,7 +47,7 @@ func (rc RedisConf) NewRedis() *Redis {
opts = append(opts, WithTLS()) opts = append(opts, WithTLS())
} }
return New(rc.Host, opts...) return newRedis(rc.Host, opts...)
} }
// Validate validates the RedisConf. // Validate validates the RedisConf.

View File

@@ -0,0 +1,5 @@
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end

View File

@@ -23,17 +23,18 @@ import (
const spanName = "redis" const spanName = "redis"
var ( var (
durationHook = hook{} defaultDurationHook = durationHook{}
redisCmdsAttributeKey = attribute.Key("redis.cmds") redisCmdsAttributeKey = attribute.Key("redis.cmds")
) )
type hook struct{} type durationHook struct {
}
func (h hook) DialHook(next red.DialHook) red.DialHook { func (h durationHook) DialHook(next red.DialHook) red.DialHook {
return next return next
} }
func (h hook) ProcessHook(next red.ProcessHook) red.ProcessHook { func (h durationHook) ProcessHook(next red.ProcessHook) red.ProcessHook {
return func(ctx context.Context, cmd red.Cmder) error { return func(ctx context.Context, cmd red.Cmder) error {
start := timex.Now() start := timex.Now()
ctx, endSpan := h.startSpan(ctx, cmd) ctx, endSpan := h.startSpan(ctx, cmd)
@@ -57,7 +58,7 @@ func (h hook) ProcessHook(next red.ProcessHook) red.ProcessHook {
} }
} }
func (h hook) ProcessPipelineHook(next red.ProcessPipelineHook) red.ProcessPipelineHook { func (h durationHook) ProcessPipelineHook(next red.ProcessPipelineHook) red.ProcessPipelineHook {
return func(ctx context.Context, cmds []red.Cmder) error { return func(ctx context.Context, cmds []red.Cmder) error {
if len(cmds) == 0 { if len(cmds) == 0 {
return next(ctx, cmds) return next(ctx, cmds)
@@ -83,6 +84,33 @@ func (h hook) ProcessPipelineHook(next red.ProcessPipelineHook) red.ProcessPipel
} }
} }
func (h durationHook) startSpan(ctx context.Context, cmds ...red.Cmder) (context.Context, func(err error)) {
tracer := trace.TracerFromContext(ctx)
ctx, span := tracer.Start(ctx,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
cmdStrs := make([]string, 0, len(cmds))
for _, cmd := range cmds {
cmdStrs = append(cmdStrs, cmd.Name())
}
span.SetAttributes(redisCmdsAttributeKey.StringSlice(cmdStrs))
return ctx, func(err error) {
defer span.End()
if err == nil || errors.Is(err, red.Nil) {
span.SetStatus(codes.Ok, "")
return
}
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}
}
func formatError(err error) string { func formatError(err error) string {
if err == nil || errors.Is(err, red.Nil) { if err == nil || errors.Is(err, red.Nil) {
return "" return ""
@@ -95,7 +123,7 @@ func formatError(err error) string {
} }
switch { switch {
case err == io.EOF: case errors.Is(err, io.EOF):
return "eof" return "eof"
case errors.Is(err, context.DeadlineExceeded): case errors.Is(err, context.DeadlineExceeded):
return "context deadline" return "context deadline"
@@ -123,30 +151,3 @@ func logDuration(ctx context.Context, cmds []red.Cmder, duration time.Duration)
} }
logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String()) logx.WithContext(ctx).WithDuration(duration).Slowf("[REDIS] slowcall on executing: %s", buf.String())
} }
func (h hook) startSpan(ctx context.Context, cmds ...red.Cmder) (context.Context, func(err error)) {
tracer := trace.TracerFromContext(ctx)
ctx, span := tracer.Start(ctx,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
cmdStrs := make([]string, 0, len(cmds))
for _, cmd := range cmds {
cmdStrs = append(cmdStrs, cmd.Name())
}
span.SetAttributes(redisCmdsAttributeKey.StringSlice(cmdStrs))
return ctx, func(err error) {
defer span.End()
if err == nil || errors.Is(err, red.Nil) {
span.SetStatus(codes.Ok, "")
return
}
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}
}

View File

@@ -21,7 +21,7 @@ func TestHookProcessCase1(t *testing.T) {
tracetest.NewInMemoryExporter(t) tracetest.NewInMemoryExporter(t)
w := logtest.NewCollector(t) w := logtest.NewCollector(t)
err := durationHook.ProcessHook(func(ctx context.Context, cmd red.Cmder) error { err := defaultDurationHook.ProcessHook(func(ctx context.Context, cmd red.Cmder) error {
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name()) assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
return nil return nil
})(context.Background(), red.NewCmd(context.Background())) })(context.Background(), red.NewCmd(context.Background()))
@@ -36,7 +36,7 @@ func TestHookProcessCase2(t *testing.T) {
tracetest.NewInMemoryExporter(t) tracetest.NewInMemoryExporter(t)
w := logtest.NewCollector(t) w := logtest.NewCollector(t)
err := durationHook.ProcessHook(func(ctx context.Context, cmd red.Cmder) error { err := defaultDurationHook.ProcessHook(func(ctx context.Context, cmd red.Cmder) error {
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name()) assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
time.Sleep(slowThreshold.Load() + time.Millisecond) time.Sleep(slowThreshold.Load() + time.Millisecond)
return nil return nil
@@ -54,12 +54,12 @@ func TestHookProcessPipelineCase1(t *testing.T) {
tracetest.NewInMemoryExporter(t) tracetest.NewInMemoryExporter(t)
w := logtest.NewCollector(t) w := logtest.NewCollector(t)
err := durationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error { err := defaultDurationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error {
return nil return nil
})(context.Background(), nil) })(context.Background(), nil)
assert.NoError(t, err) assert.NoError(t, err)
err = durationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error { err = defaultDurationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error {
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name()) assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
return nil return nil
})(context.Background(), []red.Cmder{ })(context.Background(), []red.Cmder{
@@ -74,7 +74,7 @@ func TestHookProcessPipelineCase2(t *testing.T) {
tracetest.NewInMemoryExporter(t) tracetest.NewInMemoryExporter(t)
w := logtest.NewCollector(t) w := logtest.NewCollector(t)
err := durationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error { err := defaultDurationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error {
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name()) assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
time.Sleep(slowThreshold.Load() + time.Millisecond) time.Sleep(slowThreshold.Load() + time.Millisecond)
return nil return nil
@@ -91,7 +91,7 @@ func TestHookProcessPipelineCase2(t *testing.T) {
func TestHookProcessPipelineCase3(t *testing.T) { func TestHookProcessPipelineCase3(t *testing.T) {
te := tracetest.NewInMemoryExporter(t) te := tracetest.NewInMemoryExporter(t)
err := durationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error { err := defaultDurationHook.ProcessPipelineHook(func(ctx context.Context, cmds []red.Cmder) error {
assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name()) assert.Equal(t, "redis", tracesdk.SpanFromContext(ctx).(interface{ Name() string }).Name())
return assert.AnError return assert.AnError
})(context.Background(), []red.Cmder{ })(context.Background(), []red.Cmder{

View File

@@ -0,0 +1,6 @@
if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end

View File

@@ -19,7 +19,7 @@ func TestRedisMetric(t *testing.T) {
cfg := devserver.Config{} cfg := devserver.Config{}
_ = conf.FillDefault(&cfg) _ = conf.FillDefault(&cfg)
server := devserver.NewServer(cfg) server := devserver.NewServer(cfg)
server.StartAsync() server.StartAsync(cfg)
time.Sleep(time.Second) time.Sleep(time.Second)
metricReqDur.Observe(8, "test-cmd") metricReqDur.Observe(8, "test-cmd")

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ func (m myHook) ProcessHook(next red.ProcessHook) red.ProcessHook {
if cmd.Name() == "ping" && !m.includePing { if cmd.Name() == "ping" && !m.includePing {
return next(ctx, cmd) return next(ctx, cmd)
} }
return errors.New("hook error") return errors.New("durationHook error")
} }
} }
@@ -155,7 +155,7 @@ func TestRedis_NonBlock(t *testing.T) {
t.Run("nonBlock true", func(t *testing.T) { t.Run("nonBlock true", func(t *testing.T) {
s := miniredis.RunT(t) s := miniredis.RunT(t)
// use hook to simulate redis ping error // use durationHook to simulate redis ping error
_, err := NewRedis(RedisConf{ _, err := NewRedis(RedisConf{
Host: s.Addr(), Host: s.Addr(),
NonBlock: true, NonBlock: true,

View File

@@ -37,8 +37,11 @@ func getClient(r *Redis) (*red.Client, error) {
MinIdleConns: idleConns, MinIdleConns: idleConns,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
}) })
store.AddHook(durationHook)
for _, hook := range r.hooks { hooks := append([]red.Hook{defaultDurationHook, breakerHook{
brk: r.brk,
}}, r.hooks...)
for _, hook := range hooks {
store.AddHook(hook) store.AddHook(hook)
} }

View File

@@ -33,8 +33,11 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
MinIdleConns: idleConns, MinIdleConns: idleConns,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
}) })
store.AddHook(durationHook)
for _, hook := range r.hooks { hooks := append([]red.Hook{defaultDurationHook, breakerHook{
brk: r.brk,
}}, r.hooks...)
for _, hook := range hooks {
store.AddHook(hook) store.AddHook(hook)
} }

View File

@@ -51,7 +51,7 @@ func TestGetCluster(t *testing.T) {
Addr: r.Addr(), Addr: r.Addr(),
Type: ClusterType, Type: ClusterType,
tls: true, tls: true,
hooks: []red.Hook{durationHook}, hooks: []red.Hook{defaultDurationHook},
}) })
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.NotNil(t, c) assert.NotNil(t, c)

View File

@@ -2,6 +2,8 @@ package redis
import ( import (
"context" "context"
_ "embed"
"errors"
"math/rand" "math/rand"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
@@ -19,17 +21,13 @@ const (
) )
var ( var (
lockScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then //go:embed lockscript.lua
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2]) lockLuaScript string
return "OK" lockScript = NewScript(lockLuaScript)
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) //go:embed delscript.lua
end`) delLuaScript string
delScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then delScript = NewScript(delLuaScript)
return redis.call("DEL", KEYS[1])
else
return 0
end`)
) )
// A RedisLock is a redis lock. // A RedisLock is a redis lock.
@@ -64,7 +62,7 @@ func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
resp, err := rl.store.ScriptRunCtx(ctx, lockScript, []string{rl.key}, []string{ resp, err := rl.store.ScriptRunCtx(ctx, lockScript, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance), rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
}) })
if err == red.Nil { if errors.Is(err, red.Nil) {
return false, nil return false, nil
} else if err != nil { } else if err != nil {
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error()) logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())

View File

@@ -20,7 +20,7 @@ func TestSqlxMetric(t *testing.T) {
_ = conf.FillDefault(&cfg) _ = conf.FillDefault(&cfg)
cfg.Port = 6480 cfg.Port = 6480
server := devserver.NewServer(cfg) server := devserver.NewServer(cfg)
server.StartAsync() server.StartAsync(cfg)
time.Sleep(time.Second) time.Sleep(time.Second)
metricReqDur.Observe(8, "test-cmd") metricReqDur.Observe(8, "test-cmd")

View File

@@ -13,7 +13,7 @@ const (
// NewMysql returns a mysql connection. // NewMysql returns a mysql connection.
func NewMysql(datasource string, opts ...SqlOption) SqlConn { func NewMysql(datasource string, opts ...SqlOption) SqlConn {
opts = append(opts, withMysqlAcceptable()) opts = append([]SqlOption{withMysqlAcceptable()}, opts...)
return NewSqlConn(mysqlDriverName, datasource, opts...) return NewSqlConn(mysqlDriverName, datasource, opts...)
} }

View File

@@ -2,7 +2,6 @@ package sqlx
import ( import (
"errors" "errors"
"reflect"
"testing" "testing"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
@@ -38,7 +37,6 @@ func TestBreakerOnNotHandlingDuplicateEntry(t *testing.T) {
func TestMysqlAcceptable(t *testing.T) { func TestMysqlAcceptable(t *testing.T) {
conn := NewMysql("nomysql").(*commonSqlConn) conn := NewMysql("nomysql").(*commonSqlConn)
withMysqlAcceptable()(conn) withMysqlAcceptable()(conn)
assert.EqualValues(t, reflect.ValueOf(mysqlAcceptable).Pointer(), reflect.ValueOf(conn.accept).Pointer())
assert.True(t, mysqlAcceptable(nil)) assert.True(t, mysqlAcceptable(nil))
assert.False(t, mysqlAcceptable(errors.New("any"))) assert.False(t, mysqlAcceptable(errors.New("any")))
assert.False(t, mysqlAcceptable(new(mysql.MySQLError))) assert.False(t, mysqlAcceptable(new(mysql.MySQLError)))

View File

@@ -1,6 +1,7 @@
package sqlx package sqlx
import ( import (
"context"
"errors" "errors"
"reflect" "reflect"
"strings" "strings"
@@ -87,6 +88,10 @@ func getValueInterface(value reflect.Value) (any, error) {
} }
} }
func isScanFailed(err error) bool {
return err != nil && !errors.Is(err, context.DeadlineExceeded)
}
func mapStructFieldsIntoSlice(v reflect.Value, columns []string, strict bool) ([]any, error) { func mapStructFieldsIntoSlice(v reflect.Value, columns []string, strict bool) ([]any, error) {
fields := unwrapFields(v) fields := unwrapFields(v)
if strict && len(columns) < len(fields) { if strict && len(columns) < len(fields) {
@@ -248,7 +253,7 @@ func unmarshalRows(v any, scanner rowsScanner, strict bool) error {
return ErrUnsupportedValueType return ErrUnsupportedValueType
} }
return nil return scanner.Err()
default: default:
return ErrUnsupportedValueType return ErrUnsupportedValueType
} }

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@@ -83,7 +84,7 @@ func NewSqlConn(driverName, datasource string, opts ...SqlOption) SqlConn {
} }
// NewSqlConnFromDB returns a SqlConn with the given sql.DB. // NewSqlConnFromDB returns a SqlConn with the given sql.DB.
// Use it with caution, it's provided for other ORM to interact with. // Use it with caution; it's provided for other ORM to interact with.
func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn { func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn {
conn := &commonSqlConn{ conn := &commonSqlConn{
connProv: func() (*sql.DB, error) { connProv: func() (*sql.DB, error) {
@@ -120,7 +121,7 @@ func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...any) (
endSpan(span, err) endSpan(span, err)
}() }()
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptableCtx(ctx, func() error {
var conn *sql.DB var conn *sql.DB
conn, err = db.connProv() conn, err = db.connProv()
if err != nil { if err != nil {
@@ -148,7 +149,7 @@ func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt Stm
endSpan(span, err) endSpan(span, err)
}() }()
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptableCtx(ctx, func() error {
var conn *sql.DB var conn *sql.DB
conn, err = db.connProv() conn, err = db.connProv()
if err != nil { if err != nil {
@@ -256,7 +257,7 @@ func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Contex
endSpan(span, err) endSpan(span, err)
}() }()
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptableCtx(ctx, func() error {
return transact(ctx, db, db.beginTx, fn) return transact(ctx, db, db.beginTx, fn)
}, db.acceptable) }, db.acceptable)
if errors.Is(err, breaker.ErrServiceUnavailable) { if errors.Is(err, breaker.ErrServiceUnavailable) {
@@ -267,8 +268,7 @@ func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Contex
} }
func (db *commonSqlConn) acceptable(err error) bool { func (db *commonSqlConn) acceptable(err error) bool {
if err == nil || errors.Is(err, sql.ErrNoRows) || errors.Is(err, sql.ErrTxDone) || if err == nil || errorx.In(err, sql.ErrNoRows, sql.ErrTxDone, context.Canceled) {
errors.Is(err, context.Canceled) {
return true return true
} }
@@ -287,7 +287,7 @@ func (db *commonSqlConn) acceptable(err error) bool {
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error, func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
q string, args ...any) (err error) { q string, args ...any) (err error) {
var scanFailed bool var scanFailed bool
err = db.brk.DoWithAcceptable(func() error { err = db.brk.DoWithAcceptableCtx(ctx, func() error {
conn, err := db.connProv() conn, err := db.connProv()
if err != nil { if err != nil {
db.onError(ctx, err) db.onError(ctx, err)
@@ -296,7 +296,7 @@ func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows)
return query(ctx, conn, func(rows *sql.Rows) error { return query(ctx, conn, func(rows *sql.Rows) error {
e := scanner(rows) e := scanner(rows)
if e != nil { if isScanFailed(e) {
scanFailed = true scanFailed = true
} }
return e return e
@@ -315,6 +315,13 @@ func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows)
// acceptable is the func to check if the error can be accepted. // acceptable is the func to check if the error can be accepted.
func WithAcceptable(acceptable func(err error) bool) SqlOption { func WithAcceptable(acceptable func(err error) bool) SqlOption {
return func(conn *commonSqlConn) { return func(conn *commonSqlConn) {
conn.accept = acceptable if conn.accept == nil {
conn.accept = acceptable
} else {
pre := conn.accept
conn.accept = func(err error) bool {
return pre(err) || acceptable(err)
}
}
} }
} }

View File

@@ -264,6 +264,45 @@ func TestBreakerWithScanError(t *testing.T) {
}) })
} }
func TestWithAcceptable(t *testing.T) {
var (
acceptableErr = errors.New("acceptable")
acceptableErr2 = errors.New("acceptable2")
acceptableErr3 = errors.New("acceptable3")
)
opts := []SqlOption{
WithAcceptable(func(err error) bool {
if err == nil {
return true
}
return errors.Is(err, acceptableErr)
}),
WithAcceptable(func(err error) bool {
if err == nil {
return true
}
return errors.Is(err, acceptableErr2)
}),
WithAcceptable(func(err error) bool {
if err == nil {
return true
}
return errors.Is(err, acceptableErr3)
}),
}
var conn = &commonSqlConn{}
for _, opt := range opts {
opt(conn)
}
assert.True(t, conn.accept(nil))
assert.False(t, conn.accept(assert.AnError))
assert.True(t, conn.accept(acceptableErr))
assert.True(t, conn.accept(acceptableErr2))
assert.True(t, conn.accept(acceptableErr3))
}
func buildConn() (mock sqlmock.Sqlmock, err error) { func buildConn() (mock sqlmock.Sqlmock, err error) {
_, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) { _, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
var db *sql.DB var db *sql.DB

View File

@@ -65,7 +65,7 @@ func (s statement) ExecCtx(ctx context.Context, args ...any) (result sql.Result,
endSpan(span, err) endSpan(span, err)
}() }()
err = s.brk.DoWithAcceptable(func() error { err = s.brk.DoWithAcceptableCtx(ctx, func() error {
result, err = execStmt(ctx, s.stmt, s.query, args...) result, err = execStmt(ctx, s.stmt, s.query, args...)
return err return err
}, func(err error) bool { }, func(err error) bool {
@@ -141,10 +141,10 @@ func (s statement) QueryRowsPartialCtx(ctx context.Context, v any, args ...any)
func (s statement) queryRows(ctx context.Context, scanFn func(any, rowsScanner) error, func (s statement) queryRows(ctx context.Context, scanFn func(any, rowsScanner) error,
v any, args ...any) error { v any, args ...any) error {
var scanFailed bool var scanFailed bool
err := s.brk.DoWithAcceptable(func() error { err := s.brk.DoWithAcceptableCtx(ctx, func() error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error { return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
err := scanFn(v, rows) err := scanFn(v, rows)
if err != nil { if isScanFailed(err) {
scanFailed = true scanFailed = true
} }
return err return err

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"strconv"
"testing" "testing"
"time" "time"
@@ -290,6 +291,24 @@ func TestStmtBreaker(t *testing.T) {
}) })
} }
func TestQueryRowsScanTimeout(t *testing.T) {
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{"foo"})
for i := 0; i < 10000; i++ {
rows = rows.AddRow("bar" + strconv.Itoa(i))
}
mock.ExpectQuery("any").WillReturnRows(rows)
var val []struct {
Foo string
}
conn := NewSqlConnFromDB(db)
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*2)
err := conn.QueryRowsCtx(ctx, &val, "any")
assert.ErrorIs(t, err, context.DeadlineExceeded)
cancel()
})
}
type mockedSessionConn struct { type mockedSessionConn struct {
lastInsertId int64 lastInsertId int64
rowsAffected int64 rowsAffected int64

View File

@@ -59,7 +59,13 @@ func StartAgent(c Config) {
// StopAgent shuts down the span processors in the order they were registered. // StopAgent shuts down the span processors in the order they were registered.
func StopAgent() { func StopAgent() {
_ = tp.Shutdown(context.Background()) lock.Lock()
defer lock.Unlock()
if tp != nil {
_ = tp.Shutdown(context.Background())
tp = nil
}
} }
func createExporter(c Config) (sdktrace.SpanExporter, error) { func createExporter(c Config) (sdktrace.SpanExporter, error) {

View File

@@ -34,7 +34,7 @@ type mockSpan struct {
options []trace.EventOption options []trace.EventOption
} }
func (m *mockSpan) End(options ...trace.SpanEndOption) { func (m *mockSpan) End(_ ...trace.SpanEndOption) {
} }
func (m *mockSpan) AddEvent(name string, options ...trace.EventOption) { func (m *mockSpan) AddEvent(name string, options ...trace.EventOption) {
@@ -46,20 +46,20 @@ func (m *mockSpan) IsRecording() bool {
return false return false
} }
func (m *mockSpan) RecordError(err error, options ...trace.EventOption) { func (m *mockSpan) RecordError(_ error, _ ...trace.EventOption) {
} }
func (m *mockSpan) SpanContext() trace.SpanContext { func (m *mockSpan) SpanContext() trace.SpanContext {
panic("implement me") panic("implement me")
} }
func (m *mockSpan) SetStatus(code codes.Code, description string) { func (m *mockSpan) SetStatus(_ codes.Code, _ string) {
} }
func (m *mockSpan) SetName(name string) { func (m *mockSpan) SetName(_ string) {
} }
func (m *mockSpan) SetAttributes(kv ...attribute.KeyValue) { func (m *mockSpan) SetAttributes(_ ...attribute.KeyValue) {
} }
func (m *mockSpan) TracerProvider() trace.TracerProvider { func (m *mockSpan) TracerProvider() trace.TracerProvider {

View File

@@ -4,5 +4,5 @@ import "net/http"
// TraceIdKey is the trace id header. // TraceIdKey is the trace id header.
// https://www.w3.org/TR/trace-context/#trace-id // https://www.w3.org/TR/trace-context/#trace-id
// May change it to trace-id afterwards. // May change it to trace-id afterward.
var TraceIdKey = http.CanonicalHeaderKey("x-trace-id") var TraceIdKey = http.CanonicalHeaderKey("x-trace-id")

56
go.mod
View File

@@ -4,24 +4,24 @@ go 1.19
require ( require (
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/alicebob/miniredis/v2 v2.31.1 github.com/alicebob/miniredis/v2 v2.32.1
github.com/fatih/color v1.16.0 github.com/fatih/color v1.16.0
github.com/fullstorydev/grpcurl v1.8.9 github.com/fullstorydev/grpcurl v1.9.1
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.8.1
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.3 github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.5.3 github.com/jackc/pgx/v5 v5.5.5
github.com/jhump/protoreflect v1.15.6 github.com/jhump/protoreflect v1.16.0
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/pelletier/go-toml/v2 v2.1.1 github.com/pelletier/go-toml/v2 v2.2.2
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/redis/go-redis/v9 v9.4.0 github.com/redis/go-redis/v9 v9.4.0
github.com/spaolacci/murmur3 v1.1.0 github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
go.etcd.io/etcd/api/v3 v3.5.12 go.etcd.io/etcd/api/v3 v3.5.13
go.etcd.io/etcd/client/v3 v3.5.12 go.etcd.io/etcd/client/v3 v3.5.13
go.mongodb.org/mongo-driver v1.13.1 go.mongodb.org/mongo-driver v1.13.1
go.opentelemetry.io/otel v1.19.0 go.opentelemetry.io/otel v1.19.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 go.opentelemetry.io/otel/exporters/jaeger v1.17.0
@@ -33,32 +33,36 @@ require (
go.opentelemetry.io/otel/trace v1.19.0 go.opentelemetry.io/otel/trace v1.19.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
go.uber.org/goleak v1.2.1 go.uber.org/goleak v1.2.1
golang.org/x/net v0.21.0 golang.org/x/net v0.25.0
golang.org/x/sys v0.17.0 golang.org/x/sys v0.20.0
golang.org/x/time v0.5.0 golang.org/x/time v0.5.0
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de
google.golang.org/grpc v1.62.0 google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.32.0 google.golang.org/protobuf v1.34.1
gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.29.2 k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.2 k8s.io/apimachinery v0.29.4
k8s.io/client-go v0.29.2 k8s.io/client-go v0.29.3
k8s.io/utils v0.0.0-20230726121419-3b25d923346b k8s.io/utils v0.0.0-20230726121419-3b25d923346b
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bufbuild/protocompile v0.8.0 // indirect github.com/bufbuild/protocompile v0.10.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/envoyproxy/go-control-plane v0.12.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
@@ -96,22 +100,22 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.19.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/term v0.17.0 // indirect golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.15.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.110.1 // indirect k8s.io/klog/v2 v2.110.1 // indirect

115
go.sum
View File

@@ -1,18 +1,19 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6TTnfX8Y= github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo=
github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bufbuild/protocompile v0.8.0 h1:9Kp1q6OkS9L4nM3FYbr8vlJnEwtbpDPQlQOVXfR+78s= github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM=
github.com/bufbuild/protocompile v0.8.0/go.mod h1:+Etjg4guZoAqzVk2czwEQP12yaxLJ8DxuqCJ9qHdH94= github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -20,6 +21,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -32,10 +35,14 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fullstorydev/grpcurl v1.8.9 h1:JMvZXK8lHDGyLmTQ0ZdGDnVVGuwjbpaumf8p42z0d+c= github.com/fullstorydev/grpcurl v1.9.1 h1:YxX1aCcCc4SDBQfj9uoWcTLe8t4NWrZe1y+mk83BQgo=
github.com/fullstorydev/grpcurl v1.8.9/go.mod h1:PNNKevV5VNAV2loscyLISrEnWQI61eqR0F8l3bVadAA= github.com/fullstorydev/grpcurl v1.9.1/go.mod h1:i8gKLIC6s93WdU3LSmkE5vtsCxyRmihUj5FK1cNW5EM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -48,8 +55,8 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -57,13 +64,12 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -88,12 +94,12 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jhump/protoreflect v1.15.6 h1:WMYJbw2Wo+KOWwZFvgY0jMoVHM6i4XIvRs2RcBj5VmI= github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
github.com/jhump/protoreflect v1.15.6/go.mod h1:jCHoyYQIJnaabEYnbGwyo9hUqfyUMTbJw/tAut5t97E= github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -139,8 +145,8 @@ github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -165,6 +171,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -186,14 +193,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4=
go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c=
go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8=
go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js=
go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI=
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
@@ -234,8 +241,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -248,10 +255,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -272,20 +279,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -301,18 +308,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -328,12 +335,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=

View File

@@ -2,11 +2,12 @@ package devserver
// Config is config for inner http server. // Config is config for inner http server.
type Config struct { type Config struct {
Enabled bool `json:",default=true"` Enabled bool `json:",default=true"`
Host string `json:",optional"` Host string `json:",optional"`
Port int `json:",default=6060"` Port int `json:",default=6060"`
MetricsPath string `json:",default=/metrics"` MetricsPath string `json:",default=/metrics"`
HealthPath string `json:",default=/healthz"` HealthPath string `json:",default=/healthz"`
EnableMetrics bool `json:",default=true"` EnableMetrics bool `json:",default=true"`
EnablePprof bool `json:",default=true"` EnablePprof bool `json:",default=true"`
HealthResponse string `json:",default=OK"`
} }

View File

@@ -16,8 +16,8 @@ import (
var once sync.Once var once sync.Once
// Server is inner http server, expose some useful observability information of app. // Server is an inner http server, expose some useful observability information of app.
// For example health check, metrics and pprof. // For example, health check, metrics and pprof.
type Server struct { type Server struct {
config Config config Config
server *http.ServeMux server *http.ServeMux
@@ -32,13 +32,14 @@ func NewServer(config Config) *Server {
} }
} }
func (s *Server) addRoutes() { func (s *Server) addRoutes(c Config) {
// route path, routes list // route path, routes list
s.handleFunc("/", func(w http.ResponseWriter, _ *http.Request) { s.handleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(s.routes) _ = json.NewEncoder(w).Encode(s.routes)
}) })
// health // health
s.handleFunc(s.config.HealthPath, health.CreateHttpHandler()) s.handleFunc(s.config.HealthPath, health.CreateHttpHandler(c.HealthResponse))
// metrics // metrics
if s.config.EnableMetrics { if s.config.EnableMetrics {
@@ -46,6 +47,7 @@ func (s *Server) addRoutes() {
prometheus.Enable() prometheus.Enable()
s.handleFunc(s.config.MetricsPath, promhttp.Handler().ServeHTTP) s.handleFunc(s.config.MetricsPath, promhttp.Handler().ServeHTTP)
} }
// pprof // pprof
if s.config.EnablePprof { if s.config.EnablePprof {
s.handleFunc("/debug/pprof/", pprof.Index) s.handleFunc("/debug/pprof/", pprof.Index)
@@ -62,8 +64,8 @@ func (s *Server) handleFunc(pattern string, handler http.HandlerFunc) {
} }
// StartAsync start inner http server background. // StartAsync start inner http server background.
func (s *Server) StartAsync() { func (s *Server) StartAsync(c Config) {
s.addRoutes() s.addRoutes(c)
threading.GoSafe(func() { threading.GoSafe(func() {
addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
logx.Infof("Starting dev http server at %s", addr) logx.Infof("Starting dev http server at %s", addr)
@@ -78,7 +80,7 @@ func StartAgent(c Config) {
once.Do(func() { once.Do(func() {
if c.Enabled { if c.Enabled {
s := NewServer(c) s := NewServer(c)
s.StartAsync() s.StartAsync(c)
} }
}) })
} }

View File

@@ -13,7 +13,7 @@ import (
var defaultHealthManager = newComboHealthManager() var defaultHealthManager = newComboHealthManager()
type ( type (
// Probe represents readiness status of given component. // Probe represents readiness status of a given component.
Probe interface { Probe interface {
// MarkReady sets a ready state for the endpoint handlers. // MarkReady sets a ready state for the endpoint handlers.
MarkReady() MarkReady()
@@ -44,10 +44,10 @@ func AddProbe(probe Probe) {
} }
// CreateHttpHandler create health http handler base on given probe. // CreateHttpHandler create health http handler base on given probe.
func CreateHttpHandler() http.HandlerFunc { func CreateHttpHandler(healthResponse string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) { return func(w http.ResponseWriter, _ *http.Request) {
if defaultHealthManager.IsReady() { if defaultHealthManager.IsReady() {
_, _ = w.Write([]byte("OK")) _, _ = w.Write([]byte(healthResponse))
} else { } else {
http.Error(w, "Service Unavailable\n"+defaultHealthManager.verboseInfo(), http.Error(w, "Service Unavailable\n"+defaultHealthManager.verboseInfo(),
http.StatusServiceUnavailable) http.StatusServiceUnavailable)

View File

@@ -121,7 +121,7 @@ func TestAddGlobalProbes(t *testing.T) {
func TestCreateHttpHandler(t *testing.T) { func TestCreateHttpHandler(t *testing.T) {
cleanupForTest(t) cleanupForTest(t)
srv := httptest.NewServer(CreateHttpHandler()) srv := httptest.NewServer(CreateHttpHandler("OK"))
defer srv.Close() defer srv.Close()
resp, err := http.Get(srv.URL) resp, err := http.Get(srv.URL)

View File

@@ -12,7 +12,7 @@ import (
type DepositServer struct{} type DepositServer struct{}
// Deposit handles the deposit requests. // Deposit handles the deposit requests.
func (*DepositServer) Deposit(ctx context.Context, req *DepositRequest) (*DepositResponse, error) { func (*DepositServer) Deposit(_ context.Context, req *DepositRequest) (*DepositResponse, error) {
if req.GetAmount() < 0 { if req.GetAmount() < 0 {
return nil, status.Errorf(codes.InvalidArgument, "cannot deposit %v", req.GetAmount()) return nil, status.Errorf(codes.InvalidArgument, "cannot deposit %v", req.GetAmount())
} }

View File

@@ -301,6 +301,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>97. 上海荣时信息科技有限公司 >97. 上海荣时信息科技有限公司
>98. 上海同犀智能科技有限公司 >98. 上海同犀智能科技有限公司
>99. 新华三技术有限公司 >99. 新华三技术有限公司
>100. 上海邑脉科技有限公司
>101. 上海巨瓴科技有限公司
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。 如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。

View File

@@ -217,9 +217,12 @@ func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
handler.TraceHandler(ng.conf.Name, handler.TraceHandler(ng.conf.Name,
"", "",
handler.WithTraceIgnorePaths(ng.conf.TraceIgnorePaths)), handler.WithTraceIgnorePaths(ng.conf.TraceIgnorePaths)),
ng.getLogHandler(),
) )
if ng.conf.Middlewares.Log {
chn = chn.Append(ng.getLogHandler())
}
var h http.Handler var h http.Handler
if next != nil { if next != nil {
h = chn.Then(next) h = chn.Then(next)

View File

@@ -435,7 +435,7 @@ type mockedRouter struct {
func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) { func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
} }
func (m mockedRouter) Handle(_, _ string, handler http.Handler) error { func (m mockedRouter) Handle(_, _ string, _ http.Handler) error {
return errors.New("foo") return errors.New("foo")
} }

View File

@@ -60,7 +60,8 @@ func executeCallbacks(w http.ResponseWriter, r *http.Request, next http.Handler,
} }
} }
func handleVerificationFailure(w http.ResponseWriter, r *http.Request, next http.Handler, strict bool, code int) { func handleVerificationFailure(w http.ResponseWriter, r *http.Request, next http.Handler,
strict bool, _ int) {
if strict { if strict {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} else { } else {

View File

@@ -327,7 +327,7 @@ func buildRequest(rs requestSettings) (*http.Request, error) {
query, query,
bodySign, bodySign,
}, "\n") }, "\n")
rs.signature = codec.HmacBase64([]byte(key), contentOfSign) rs.signature = codec.HmacBase64(key, contentOfSign)
} }
var mode string var mode string

Some files were not shown because too many files have changed in this diff Show More