mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-28 00:55:28 +08:00
Compare commits
45 Commits
tools/goct
...
v1.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25821bdee6 | ||
|
|
b624b966f0 | ||
|
|
df96262235 | ||
|
|
2629636f64 | ||
|
|
708ad207d7 | ||
|
|
b53ba76a99 | ||
|
|
be7f93924a | ||
|
|
45be48a4ee | ||
|
|
e08ba2fee8 | ||
|
|
a5d2b971a1 | ||
|
|
9763c8b143 | ||
|
|
4e3f1776dc | ||
|
|
e38036cea2 | ||
|
|
8e97c5819f | ||
|
|
0ee44c7064 | ||
|
|
a1bacd3fc8 | ||
|
|
c98d5fdaf4 | ||
|
|
2ee43b41b8 | ||
|
|
8367af3416 | ||
|
|
03b6e377d7 | ||
|
|
ec41880476 | ||
|
|
5263805b3b | ||
|
|
a7363f0c21 | ||
|
|
52e5d85221 | ||
|
|
88aab8f635 | ||
|
|
1f63cbe9c6 | ||
|
|
0dfaf135dd | ||
|
|
914bcdcf2b | ||
|
|
e38cb0118d | ||
|
|
cb8161c799 | ||
|
|
c4dac2095f | ||
|
|
25a807afb2 | ||
|
|
6be37ad533 | ||
|
|
28cb2c5804 | ||
|
|
0f1d4c6bca | ||
|
|
bfe8335cb2 | ||
|
|
3c10ce0115 | ||
|
|
1303e0fe6f | ||
|
|
9c17499757 | ||
|
|
8ceb2885db | ||
|
|
00944894b4 | ||
|
|
609fb3d59e | ||
|
|
01c330abe7 | ||
|
|
2ccef5bb4f | ||
|
|
10f1d93e2a |
@@ -1 +1,7 @@
|
|||||||
**/.git
|
**/.git
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
goctl
|
||||||
|
Makefile
|
||||||
|
readme.md
|
||||||
|
readme-cn.md
|
||||||
|
|||||||
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
- package-ecosystem: "docker" # Update image tags in Dockerfile
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions" # Update GitHub Actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
- package-ecosystem: "gomod" # See documentation for possible values
|
- package-ecosystem: "gomod" # See documentation for possible values
|
||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -64,4 +64,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
@@ -15,9 +15,9 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: '1.19'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
id: go
|
id: go
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
|
|
||||||
test-win:
|
test-win:
|
||||||
name: Windows
|
name: Windows
|
||||||
@@ -50,10 +50,10 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
# use 1.19 to guarantee Go 1.19 compatibility
|
# use 1.19 to guarantee Go 1.19 compatibility
|
||||||
go-version: 1.19
|
go-version: '1.19'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/issues.yml
vendored
2
.github/workflows/issues.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
close-issues:
|
close-issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
days-before-issue-stale: 365
|
days-before-issue-stale: 365
|
||||||
days-before-issue-close: 90
|
days-before-issue-close: 90
|
||||||
|
|||||||
@@ -53,16 +53,19 @@ type (
|
|||||||
// 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 func(err error) error) error
|
DoWithFallback(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.
|
||||||
// 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.
|
||||||
// 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 func(err error) error, acceptable Acceptable) error
|
DoWithFallbackAcceptable(req func() error, fallback Fallback, acceptable Acceptable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback is the func to be called if the request is rejected.
|
||||||
|
Fallback func(err error) error
|
||||||
|
|
||||||
// Option defines the method to customize a Breaker.
|
// Option defines the method to customize a Breaker.
|
||||||
Option func(breaker *circuitBreaker)
|
Option func(breaker *circuitBreaker)
|
||||||
|
|
||||||
@@ -86,12 +89,12 @@ type (
|
|||||||
|
|
||||||
internalThrottle interface {
|
internalThrottle interface {
|
||||||
allow() (internalPromise, error)
|
allow() (internalPromise, error)
|
||||||
doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
doReq(req func() error, fallback Fallback, acceptable Acceptable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
throttle interface {
|
throttle interface {
|
||||||
allow() (Promise, error)
|
allow() (Promise, error)
|
||||||
doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
doReq(req func() error, fallback Fallback, acceptable Acceptable) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,11 +125,11 @@ func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptab
|
|||||||
return cb.throttle.doReq(req, nil, acceptable)
|
return cb.throttle.doReq(req, nil, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *circuitBreaker) DoWithFallback(req func() error, fallback func(err error) error) 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) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
|
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)
|
||||||
}
|
}
|
||||||
@@ -168,7 +171,7 @@ func (lt loggedThrottle) allow() (Promise, error) {
|
|||||||
}, lt.logError(err)
|
}, lt.logError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
|
func (lt loggedThrottle) doReq(req func() error, fallback Fallback, acceptable Acceptable) error {
|
||||||
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
|
return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
|
||||||
accept := acceptable(err)
|
accept := acceptable(err)
|
||||||
if !accept && err != nil {
|
if !accept && err != nil {
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ func DoWithAcceptable(name string, req func() error, acceptable Acceptable) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 func(err error) error) 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 {
|
||||||
return b.DoWithFallback(req, fallback)
|
return b.DoWithFallback(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 func(err error) error,
|
func DoWithFallbackAcceptable(name string, req func() error, fallback Fallback,
|
||||||
acceptable Acceptable) error {
|
acceptable Acceptable) error {
|
||||||
return do(name, func(b Breaker) error {
|
return do(name, func(b Breaker) error {
|
||||||
return b.DoWithFallbackAcceptable(req, fallback, acceptable)
|
return b.DoWithFallbackAcceptable(req, fallback, acceptable)
|
||||||
@@ -59,7 +59,7 @@ func GetBreaker(name string) Breaker {
|
|||||||
// NoBreakerFor disables the circuit breaker for the given name.
|
// NoBreakerFor disables the circuit breaker for the given name.
|
||||||
func NoBreakerFor(name string) {
|
func NoBreakerFor(name string) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
breakers[name] = newNopBreaker()
|
breakers[name] = NopBreaker()
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package breaker
|
package breaker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/collection"
|
"github.com/zeromicro/go-zero/core/collection"
|
||||||
@@ -38,7 +37,8 @@ func (b *googleBreaker) accept() error {
|
|||||||
accepts, total := b.history()
|
accepts, total := b.history()
|
||||||
weightedAccepts := b.k * float64(accepts)
|
weightedAccepts := b.k * float64(accepts)
|
||||||
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
|
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
|
||||||
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
|
// for better performance, no need to care about negative ratio
|
||||||
|
dropRatio := (float64(total-protection) - weightedAccepts) / float64(total+1)
|
||||||
if dropRatio <= 0 {
|
if dropRatio <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,9 @@ func (b *googleBreaker) allow() (internalPromise, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, 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()
|
||||||
if fallback != nil {
|
if fallback != nil {
|
||||||
return fallback(err)
|
return fallback(err)
|
||||||
}
|
}
|
||||||
@@ -69,18 +70,19 @@ func (b *googleBreaker) doReq(req func() error, fallback func(err error) error,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success bool
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
// if req() panic, success is false, mark as failure
|
||||||
|
if success {
|
||||||
|
b.markSuccess()
|
||||||
|
} else {
|
||||||
b.markFailure()
|
b.markFailure()
|
||||||
panic(e)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := req()
|
err := req()
|
||||||
if acceptable(err) {
|
if acceptable(err) {
|
||||||
b.markSuccess()
|
success = true
|
||||||
} else {
|
|
||||||
b.markFailure()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func BenchmarkGoogleBreakerAllow(b *testing.B) {
|
|||||||
breaker := getGoogleBreaker()
|
breaker := getGoogleBreaker()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i <= b.N; i++ {
|
for i := 0; i <= b.N; i++ {
|
||||||
breaker.accept()
|
_ = breaker.accept()
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
breaker.markSuccess()
|
breaker.markSuccess()
|
||||||
} else {
|
} else {
|
||||||
@@ -215,6 +215,16 @@ func BenchmarkGoogleBreakerAllow(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkGoogleBreakerDoReq(b *testing.B) {
|
||||||
|
breaker := getGoogleBreaker()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i <= b.N; i++ {
|
||||||
|
_ = breaker.doReq(func() error {
|
||||||
|
return nil
|
||||||
|
}, nil, defaultAcceptable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func markSuccess(b *googleBreaker, count int) {
|
func markSuccess(b *googleBreaker, count int) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
p, err := b.allow()
|
p, err := b.allow()
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ const nopBreakerName = "nopBreaker"
|
|||||||
|
|
||||||
type nopBreaker struct{}
|
type nopBreaker struct{}
|
||||||
|
|
||||||
func newNopBreaker() Breaker {
|
// NopBreaker returns a breaker that never trigger breaker circuit.
|
||||||
|
func NopBreaker() Breaker {
|
||||||
return nopBreaker{}
|
return nopBreaker{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,12 +25,11 @@ func (b nopBreaker) DoWithAcceptable(req func() error, _ Acceptable) error {
|
|||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b nopBreaker) DoWithFallback(req func() error, _ func(err error) error) error {
|
func (b nopBreaker) DoWithFallback(req func() error, _ Fallback) error {
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b nopBreaker) DoWithFallbackAcceptable(req func() error, _ func(err error) error,
|
func (b nopBreaker) DoWithFallbackAcceptable(req func() error, _ Fallback, _ Acceptable) error {
|
||||||
_ Acceptable) error {
|
|
||||||
return req()
|
return req()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNopBreaker(t *testing.T) {
|
func TestNopBreaker(t *testing.T) {
|
||||||
b := newNopBreaker()
|
b := NopBreaker()
|
||||||
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)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type RestfulConf struct {
|
|||||||
MaxConns int `json:",default=10000"`
|
MaxConns int `json:",default=10000"`
|
||||||
MaxBytes int64 `json:",default=1048576"`
|
MaxBytes int64 `json:",default=1048576"`
|
||||||
Timeout time.Duration `json:",default=3s"`
|
Timeout time.Duration `json:",default=3s"`
|
||||||
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
CpuThreshold int64 `json:",default=900,range=[0:1000)"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package errorx
|
package errorx
|
||||||
|
|
||||||
import "bytes"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A BatchError is an error that can hold multiple errors.
|
// A BatchError is an error that can hold multiple errors.
|
||||||
BatchError struct {
|
BatchError struct {
|
||||||
errs errorArray
|
errs errorArray
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
errorArray []error
|
errorArray []error
|
||||||
@@ -13,6 +17,9 @@ type (
|
|||||||
|
|
||||||
// Add adds errs to be, nil errors are ignored.
|
// Add adds errs to be, nil errors are ignored.
|
||||||
func (be *BatchError) Add(errs ...error) {
|
func (be *BatchError) Add(errs ...error) {
|
||||||
|
be.lock.Lock()
|
||||||
|
defer be.lock.Unlock()
|
||||||
|
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
be.errs = append(be.errs, err)
|
be.errs = append(be.errs, err)
|
||||||
@@ -22,6 +29,9 @@ func (be *BatchError) Add(errs ...error) {
|
|||||||
|
|
||||||
// Err returns an error that represents all errors.
|
// Err returns an error that represents all errors.
|
||||||
func (be *BatchError) Err() error {
|
func (be *BatchError) Err() error {
|
||||||
|
be.lock.Lock()
|
||||||
|
defer be.lock.Unlock()
|
||||||
|
|
||||||
switch len(be.errs) {
|
switch len(be.errs) {
|
||||||
case 0:
|
case 0:
|
||||||
return nil
|
return nil
|
||||||
@@ -34,6 +44,9 @@ func (be *BatchError) Err() error {
|
|||||||
|
|
||||||
// NotNil checks if any error inside.
|
// NotNil checks if any error inside.
|
||||||
func (be *BatchError) NotNil() bool {
|
func (be *BatchError) NotNil() bool {
|
||||||
|
be.lock.Lock()
|
||||||
|
defer be.lock.Unlock()
|
||||||
|
|
||||||
return len(be.errs) > 0
|
return len(be.errs) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package errorx
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -33,7 +34,7 @@ func TestBatchErrorNilFromFunc(t *testing.T) {
|
|||||||
func TestBatchErrorOneError(t *testing.T) {
|
func TestBatchErrorOneError(t *testing.T) {
|
||||||
var batch BatchError
|
var batch BatchError
|
||||||
batch.Add(errors.New(err1))
|
batch.Add(errors.New(err1))
|
||||||
assert.NotNil(t, batch)
|
assert.NotNil(t, batch.Err())
|
||||||
assert.Equal(t, err1, batch.Err().Error())
|
assert.Equal(t, err1, batch.Err().Error())
|
||||||
assert.True(t, batch.NotNil())
|
assert.True(t, batch.NotNil())
|
||||||
}
|
}
|
||||||
@@ -42,7 +43,26 @@ func TestBatchErrorWithErrors(t *testing.T) {
|
|||||||
var batch BatchError
|
var batch BatchError
|
||||||
batch.Add(errors.New(err1))
|
batch.Add(errors.New(err1))
|
||||||
batch.Add(errors.New(err2))
|
batch.Add(errors.New(err2))
|
||||||
assert.NotNil(t, batch)
|
assert.NotNil(t, batch.Err())
|
||||||
assert.Equal(t, fmt.Sprintf("%s\n%s", err1, err2), batch.Err().Error())
|
assert.Equal(t, fmt.Sprintf("%s\n%s", err1, err2), batch.Err().Error())
|
||||||
assert.True(t, batch.NotNil())
|
assert.True(t, batch.NotNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBatchErrorConcurrentAdd(t *testing.T) {
|
||||||
|
const count = 10000
|
||||||
|
var batch BatchError
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
batch.Add(errors.New(err1))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
assert.NotNil(t, batch.Err())
|
||||||
|
assert.Equal(t, count, len(batch.errs))
|
||||||
|
assert.True(t, batch.NotNil())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/errorx"
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
@@ -14,9 +15,10 @@ type (
|
|||||||
RetryOption func(*retryOptions)
|
RetryOption func(*retryOptions)
|
||||||
|
|
||||||
retryOptions struct {
|
retryOptions struct {
|
||||||
times int
|
times int
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
ignoreErrors []error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,6 +64,11 @@ func retry(ctx context.Context, fn func(errChan chan error, retryCount int), opt
|
|||||||
select {
|
select {
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
for _, ignoreErr := range options.ignoreErrors {
|
||||||
|
if errors.Is(err, ignoreErr) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
berr.Add(err)
|
berr.Add(err)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@@ -84,19 +91,28 @@ func retry(ctx context.Context, fn func(errChan chan error, retryCount int), opt
|
|||||||
return berr.Err()
|
return berr.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithRetry customize a DoWithRetry call with given retry times.
|
// WithIgnoreErrors Ignore the specified errors
|
||||||
func WithRetry(times int) RetryOption {
|
func WithIgnoreErrors(ignoreErrors []error) RetryOption {
|
||||||
return func(options *retryOptions) {
|
return func(options *retryOptions) {
|
||||||
options.times = times
|
options.ignoreErrors = ignoreErrors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInterval customizes a DoWithRetry call with given interval.
|
||||||
func WithInterval(interval time.Duration) RetryOption {
|
func WithInterval(interval time.Duration) RetryOption {
|
||||||
return func(options *retryOptions) {
|
return func(options *retryOptions) {
|
||||||
options.interval = interval
|
options.interval = interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithRetry customizes a DoWithRetry call with given retry times.
|
||||||
|
func WithRetry(times int) RetryOption {
|
||||||
|
return func(options *retryOptions) {
|
||||||
|
options.times = times
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout customizes a DoWithRetry call with given timeout.
|
||||||
func WithTimeout(timeout time.Duration) RetryOption {
|
func WithTimeout(timeout time.Duration) RetryOption {
|
||||||
return func(options *retryOptions) {
|
return func(options *retryOptions) {
|
||||||
options.timeout = timeout
|
options.timeout = timeout
|
||||||
|
|||||||
@@ -97,6 +97,24 @@ func TestRetryWithInterval(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRetryWithWithIgnoreErrors(t *testing.T) {
|
||||||
|
ignoreErr1 := errors.New("ignore error1")
|
||||||
|
ignoreErr2 := errors.New("ignore error2")
|
||||||
|
ignoreErrs := []error{ignoreErr1, ignoreErr2}
|
||||||
|
|
||||||
|
assert.Nil(t, DoWithRetry(func() error {
|
||||||
|
return ignoreErr1
|
||||||
|
}, WithIgnoreErrors(ignoreErrs)))
|
||||||
|
|
||||||
|
assert.Nil(t, DoWithRetry(func() error {
|
||||||
|
return ignoreErr2
|
||||||
|
}, WithIgnoreErrors(ignoreErrs)))
|
||||||
|
|
||||||
|
assert.NotNil(t, DoWithRetry(func() error {
|
||||||
|
return errors.New("any")
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func TestRetryCtx(t *testing.T) {
|
func TestRetryCtx(t *testing.T) {
|
||||||
t.Run("with timeout", func(t *testing.T) {
|
t.Run("with timeout", func(t *testing.T) {
|
||||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/collection"
|
"github.com/zeromicro/go-zero/core/collection"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"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"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
"github.com/zeromicro/go-zero/core/timex"
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
@@ -21,8 +22,11 @@ const (
|
|||||||
defaultCpuThreshold = 900
|
defaultCpuThreshold = 900
|
||||||
defaultMinRt = float64(time.Second / time.Millisecond)
|
defaultMinRt = float64(time.Second / time.Millisecond)
|
||||||
// moving average hyperparameter beta for calculating requests on the fly
|
// moving average hyperparameter beta for calculating requests on the fly
|
||||||
flyingBeta = 0.9
|
flyingBeta = 0.9
|
||||||
coolOffDuration = time.Second
|
coolOffDuration = time.Second
|
||||||
|
cpuMax = 1000 // millicpu
|
||||||
|
millisecondsPerSecond = 1000
|
||||||
|
overloadFactorLowerBound = 0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -66,7 +70,7 @@ type (
|
|||||||
|
|
||||||
adaptiveShedder struct {
|
adaptiveShedder struct {
|
||||||
cpuThreshold int64
|
cpuThreshold int64
|
||||||
windows int64
|
windowScale float64
|
||||||
flying int64
|
flying int64
|
||||||
avgFlying float64
|
avgFlying float64
|
||||||
avgFlyingLock syncx.SpinLock
|
avgFlyingLock syncx.SpinLock
|
||||||
@@ -105,7 +109,7 @@ func NewAdaptiveShedder(opts ...ShedderOption) Shedder {
|
|||||||
bucketDuration := options.window / time.Duration(options.buckets)
|
bucketDuration := options.window / time.Duration(options.buckets)
|
||||||
return &adaptiveShedder{
|
return &adaptiveShedder{
|
||||||
cpuThreshold: options.cpuThreshold,
|
cpuThreshold: options.cpuThreshold,
|
||||||
windows: int64(time.Second / bucketDuration),
|
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(options.buckets, bucketDuration,
|
||||||
@@ -149,16 +153,17 @@ func (as *adaptiveShedder) highThru() bool {
|
|||||||
as.avgFlyingLock.Lock()
|
as.avgFlyingLock.Lock()
|
||||||
avgFlying := as.avgFlying
|
avgFlying := as.avgFlying
|
||||||
as.avgFlyingLock.Unlock()
|
as.avgFlyingLock.Unlock()
|
||||||
maxFlight := as.maxFlight()
|
maxFlight := as.maxFlight() * as.overloadFactor()
|
||||||
return int64(avgFlying) > maxFlight && atomic.LoadInt64(&as.flying) > maxFlight
|
return avgFlying > maxFlight && float64(atomic.LoadInt64(&as.flying)) > maxFlight
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *adaptiveShedder) maxFlight() int64 {
|
func (as *adaptiveShedder) maxFlight() float64 {
|
||||||
// windows = buckets per second
|
// windows = buckets per second
|
||||||
// maxQPS = maxPASS * windows
|
// maxQPS = maxPASS * windows
|
||||||
// minRT = min average response time in milliseconds
|
// minRT = min average response time in milliseconds
|
||||||
// maxQPS * minRT / milliseconds_per_second
|
// allowedFlying = maxQPS * minRT / milliseconds_per_second
|
||||||
return int64(math.Max(1, float64(as.maxPass()*as.windows)*(as.minRt()/1e3)))
|
maxFlight := float64(as.maxPass()) * as.minRt() * as.windowScale
|
||||||
|
return mathx.AtLeast(maxFlight, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *adaptiveShedder) maxPass() int64 {
|
func (as *adaptiveShedder) maxPass() int64 {
|
||||||
@@ -174,6 +179,8 @@ func (as *adaptiveShedder) maxPass() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (as *adaptiveShedder) minRt() float64 {
|
func (as *adaptiveShedder) minRt() float64 {
|
||||||
|
// if no requests in previous windows, return defaultMinRt,
|
||||||
|
// 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) {
|
||||||
@@ -190,6 +197,13 @@ func (as *adaptiveShedder) minRt() float64 {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as *adaptiveShedder) overloadFactor() float64 {
|
||||||
|
// as.cpuThreshold must be less than cpuMax
|
||||||
|
factor := (cpuMax - float64(stat.CpuUsage())) / (cpuMax - float64(as.cpuThreshold))
|
||||||
|
// at least accept 10% of acceptable requests even cpu is highly overloaded.
|
||||||
|
return mathx.Between(factor, overloadFactorLowerBound, 1)
|
||||||
|
}
|
||||||
|
|
||||||
func (as *adaptiveShedder) shouldDrop() bool {
|
func (as *adaptiveShedder) shouldDrop() bool {
|
||||||
if as.systemOverloaded() || as.stillHot() {
|
if as.systemOverloaded() || as.stillHot() {
|
||||||
if as.highThru() {
|
if as.highThru() {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
buckets = 10
|
buckets = 10
|
||||||
bucketDuration = time.Millisecond * 50
|
bucketDuration = time.Millisecond * 50
|
||||||
|
windowFactor = 0.01
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -114,10 +115,10 @@ func TestAdaptiveShedderMaxFlight(t *testing.T) {
|
|||||||
shedder := &adaptiveShedder{
|
shedder := &adaptiveShedder{
|
||||||
passCounter: passCounter,
|
passCounter: passCounter,
|
||||||
rtCounter: rtCounter,
|
rtCounter: rtCounter,
|
||||||
windows: buckets,
|
windowScale: windowFactor,
|
||||||
droppedRecently: syncx.NewAtomicBool(),
|
droppedRecently: syncx.NewAtomicBool(),
|
||||||
}
|
}
|
||||||
assert.Equal(t, int64(54), shedder.maxFlight())
|
assert.Equal(t, float64(54), shedder.maxFlight())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
||||||
@@ -136,7 +137,7 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
|||||||
shedder := &adaptiveShedder{
|
shedder := &adaptiveShedder{
|
||||||
passCounter: passCounter,
|
passCounter: passCounter,
|
||||||
rtCounter: rtCounter,
|
rtCounter: rtCounter,
|
||||||
windows: buckets,
|
windowScale: windowFactor,
|
||||||
overloadTime: syncx.NewAtomicDuration(),
|
overloadTime: syncx.NewAtomicDuration(),
|
||||||
droppedRecently: syncx.NewAtomicBool(),
|
droppedRecently: syncx.NewAtomicBool(),
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,8 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
|||||||
|
|
||||||
// cpu >= 800, inflight > maxPass
|
// cpu >= 800, inflight > maxPass
|
||||||
shedder.avgFlying = 80
|
shedder.avgFlying = 80
|
||||||
shedder.flying = 50
|
// because of the overloadFactor, so we need to make sure maxFlight is greater than flying
|
||||||
|
shedder.flying = int64(shedder.maxFlight()*shedder.overloadFactor()) - 5
|
||||||
assert.False(t, shedder.shouldDrop())
|
assert.False(t, shedder.shouldDrop())
|
||||||
|
|
||||||
// cpu >= 800, inflight > maxPass
|
// cpu >= 800, inflight > maxPass
|
||||||
@@ -190,7 +192,7 @@ func TestAdaptiveShedderStillHot(t *testing.T) {
|
|||||||
shedder := &adaptiveShedder{
|
shedder := &adaptiveShedder{
|
||||||
passCounter: passCounter,
|
passCounter: passCounter,
|
||||||
rtCounter: rtCounter,
|
rtCounter: rtCounter,
|
||||||
windows: buckets,
|
windowScale: windowFactor,
|
||||||
overloadTime: syncx.NewAtomicDuration(),
|
overloadTime: syncx.NewAtomicDuration(),
|
||||||
droppedRecently: syncx.ForAtomicBool(true),
|
droppedRecently: syncx.ForAtomicBool(true),
|
||||||
}
|
}
|
||||||
@@ -239,6 +241,30 @@ func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
|
|||||||
b.Run("low load", bench)
|
b.Run("low load", bench)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkMaxFlight(b *testing.B) {
|
||||||
|
passCounter := newRollingWindow()
|
||||||
|
rtCounter := newRollingWindow()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(bucketDuration)
|
||||||
|
}
|
||||||
|
passCounter.Add(float64((i + 1) * 100))
|
||||||
|
for j := i*10 + 1; j <= i*10+10; j++ {
|
||||||
|
rtCounter.Add(float64(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shedder := &adaptiveShedder{
|
||||||
|
passCounter: passCounter,
|
||||||
|
rtCounter: rtCounter,
|
||||||
|
windowScale: windowFactor,
|
||||||
|
droppedRecently: syncx.NewAtomicBool(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = shedder.maxFlight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newRollingWindow() *collection.RollingWindow {
|
func newRollingWindow() *collection.RollingWindow {
|
||||||
return collection.NewRollingWindow(buckets, bucketDuration, collection.IgnoreCurrentBucket())
|
return collection.NewRollingWindow(buckets, bucketDuration, collection.IgnoreCurrentBucket())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ import (
|
|||||||
const callerDepth = 4
|
const callerDepth = 4
|
||||||
|
|
||||||
var (
|
var (
|
||||||
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||||
logLevel uint32
|
|
||||||
encoding uint32 = jsonEncodingType
|
encoding uint32 = jsonEncodingType
|
||||||
// maxContentLength is used to truncate the log content, 0 for not truncating.
|
// maxContentLength is used to truncate the log content, 0 for not truncating.
|
||||||
maxContentLength uint32
|
maxContentLength uint32
|
||||||
// use uint32 for atomic operations
|
// use uint32 for atomic operations
|
||||||
disableLog uint32
|
|
||||||
disableStat uint32
|
disableStat uint32
|
||||||
|
logLevel uint32
|
||||||
options logOptions
|
options logOptions
|
||||||
writer = new(atomicWriter)
|
writer = new(atomicWriter)
|
||||||
setupOnce sync.Once
|
setupOnce sync.Once
|
||||||
@@ -96,7 +95,7 @@ func Debugw(msg string, fields ...LogField) {
|
|||||||
|
|
||||||
// Disable disables the logging.
|
// Disable disables the logging.
|
||||||
func Disable() {
|
func Disable() {
|
||||||
atomic.StoreUint32(&disableLog, 1)
|
atomic.StoreUint32(&logLevel, disableLevel)
|
||||||
writer.Store(nopWriter{})
|
writer.Store(nopWriter{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +249,7 @@ func SetLevel(level uint32) {
|
|||||||
|
|
||||||
// SetWriter sets the logging writer. It can be used to customize the logging.
|
// SetWriter sets the logging writer. It can be used to customize the logging.
|
||||||
func SetWriter(w Writer) {
|
func SetWriter(w Writer) {
|
||||||
if atomic.LoadUint32(&disableLog) == 0 {
|
if atomic.LoadUint32(&logLevel) != disableLevel {
|
||||||
writer.Store(w)
|
writer.Store(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -666,6 +666,7 @@ func TestDisable(t *testing.T) {
|
|||||||
WithMaxSize(1024)(&opt)
|
WithMaxSize(1024)(&opt)
|
||||||
assert.Nil(t, Close())
|
assert.Nil(t, Close())
|
||||||
assert.Nil(t, Close())
|
assert.Nil(t, Close())
|
||||||
|
assert.Equal(t, uint32(disableLevel), atomic.LoadUint32(&logLevel))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDisableStat(t *testing.T) {
|
func TestDisableStat(t *testing.T) {
|
||||||
@@ -680,7 +681,7 @@ func TestDisableStat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetWriter(t *testing.T) {
|
func TestSetWriter(t *testing.T) {
|
||||||
atomic.StoreUint32(&disableLog, 0)
|
atomic.StoreUint32(&logLevel, 0)
|
||||||
Reset()
|
Reset()
|
||||||
SetWriter(nopWriter{})
|
SetWriter(nopWriter{})
|
||||||
assert.NotNil(t, writer.Load())
|
assert.NotNil(t, writer.Load())
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ const (
|
|||||||
ErrorLevel
|
ErrorLevel
|
||||||
// SevereLevel only log severe messages
|
// SevereLevel only log severe messages
|
||||||
SevereLevel
|
SevereLevel
|
||||||
|
// disableLevel doesn't log any messages
|
||||||
|
disableLevel = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -223,11 +223,11 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
|||||||
switch v := mapValue.(type) {
|
switch v := mapValue.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil {
|
if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil {
|
||||||
return err
|
return fmt.Errorf("fullName: `%s`, error: `%w`", fullName, err)
|
||||||
}
|
}
|
||||||
case string:
|
case string:
|
||||||
if err := jsonx.UnmarshalFromString(v, &slice); err != nil {
|
if err := jsonx.UnmarshalFromString(v, &slice); err != nil {
|
||||||
return err
|
return fmt.Errorf("fullName: `%s`, error: `%w`", fullName, err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return errUnsupportedType
|
return errUnsupportedType
|
||||||
@@ -428,6 +428,10 @@ func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Value
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.opts.fillDefault {
|
||||||
|
return key, &options.fieldOptionsWithContext, nil
|
||||||
|
}
|
||||||
|
|
||||||
optsWithContext, err := options.toOptionsWithContext(key, m, fullName)
|
optsWithContext, err := options.toOptionsWithContext(key, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
|
|||||||
@@ -5411,6 +5411,15 @@ func TestFillDefaultUnmarshal(t *testing.T) {
|
|||||||
assert.Equal(t, "c", st.C)
|
assert.Equal(t, "c", st.C)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("optional !", func(t *testing.T) {
|
||||||
|
var st struct {
|
||||||
|
A string `json:",optional"`
|
||||||
|
B string `json:",optional=!A"`
|
||||||
|
}
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("has value", func(t *testing.T) {
|
t.Run("has value", func(t *testing.T) {
|
||||||
type St struct {
|
type St struct {
|
||||||
A string `json:",default=a"`
|
A string `json:",default=a"`
|
||||||
|
|||||||
@@ -15,3 +15,17 @@ func TestCalcEntropy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.True(t, CalcEntropy(m) > .99)
|
assert.True(t, CalcEntropy(m) > .99)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCalcEmptyEntropy(t *testing.T) {
|
||||||
|
m := make(map[any]int)
|
||||||
|
assert.Equal(t, float64(1), CalcEntropy(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalcDiffEntropy(t *testing.T) {
|
||||||
|
const total = 1000
|
||||||
|
m := make(map[any]int, total)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
m[i] = i
|
||||||
|
}
|
||||||
|
assert.True(t, CalcEntropy(m) < .99)
|
||||||
|
}
|
||||||
|
|||||||
34
core/mathx/range.go
Normal file
34
core/mathx/range.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mathx
|
||||||
|
|
||||||
|
type numerical interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||||
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
|
||||||
|
~float32 | ~float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtLeast returns the greater of x or lower.
|
||||||
|
func AtLeast[T numerical](x, lower T) T {
|
||||||
|
if x < lower {
|
||||||
|
return lower
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtMost returns the smaller of x or upper.
|
||||||
|
func AtMost[T numerical](x, upper T) T {
|
||||||
|
if x > upper {
|
||||||
|
return upper
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Between returns the value of x clamped to the range [lower, upper].
|
||||||
|
func Between[T numerical](x, lower, upper T) T {
|
||||||
|
if x < lower {
|
||||||
|
return lower
|
||||||
|
}
|
||||||
|
if x > upper {
|
||||||
|
return upper
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
513
core/mathx/range_test.go
Normal file
513
core/mathx/range_test.go
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
package mathx
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAtLeast(t *testing.T) {
|
||||||
|
t.Run("test int", func(t *testing.T) {
|
||||||
|
if got := AtLeast(10, 5); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(3, 5); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(5, 5); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int8", func(t *testing.T) {
|
||||||
|
if got := AtLeast(int8(10), int8(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int8(3), int8(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int8(5), int8(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int16", func(t *testing.T) {
|
||||||
|
if got := AtLeast(int16(10), int16(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int16(3), int16(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int16(5), int16(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int32", func(t *testing.T) {
|
||||||
|
if got := AtLeast(int32(10), int32(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int32(3), int32(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int32(5), int32(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int64", func(t *testing.T) {
|
||||||
|
if got := AtLeast(int64(10), int64(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int64(3), int64(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(int64(5), int64(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint", func(t *testing.T) {
|
||||||
|
if got := AtLeast(uint(10), uint(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint(3), uint(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint(5), uint(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint8", func(t *testing.T) {
|
||||||
|
if got := AtLeast(uint8(10), uint8(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint8(3), uint8(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint8(5), uint8(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint16", func(t *testing.T) {
|
||||||
|
if got := AtLeast(uint16(10), uint16(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint16(3), uint16(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint16(5), uint16(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint32", func(t *testing.T) {
|
||||||
|
if got := AtLeast(uint32(10), uint32(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint32(3), uint32(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint32(5), uint32(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint64", func(t *testing.T) {
|
||||||
|
if got := AtLeast(uint64(10), uint64(5)); got != 10 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint64(3), uint64(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(uint64(5), uint64(5)); got != 5 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float32", func(t *testing.T) {
|
||||||
|
if got := AtLeast(float32(10.0), float32(5.0)); got != 10.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10.0", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(float32(3.0), float32(5.0)); got != 5.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(float32(5.0), float32(5.0)); got != 5.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float64", func(t *testing.T) {
|
||||||
|
if got := AtLeast(10.0, 5.0); got != 10.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 10.0", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(3.0, 5.0); got != 5.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := AtLeast(5.0, 5.0); got != 5.0 {
|
||||||
|
t.Errorf("AtLeast() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtMost(t *testing.T) {
|
||||||
|
t.Run("test int", func(t *testing.T) {
|
||||||
|
if got := AtMost(10, 5); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(3, 5); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(5, 5); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int8", func(t *testing.T) {
|
||||||
|
if got := AtMost(int8(10), int8(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int8(3), int8(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int8(5), int8(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int16", func(t *testing.T) {
|
||||||
|
if got := AtMost(int16(10), int16(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int16(3), int16(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int16(5), int16(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int32", func(t *testing.T) {
|
||||||
|
if got := AtMost(int32(10), int32(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int32(3), int32(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int32(5), int32(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int64", func(t *testing.T) {
|
||||||
|
if got := AtMost(int64(10), int64(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int64(3), int64(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(int64(5), int64(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint", func(t *testing.T) {
|
||||||
|
if got := AtMost(uint(10), uint(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint(3), uint(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint(5), uint(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint8", func(t *testing.T) {
|
||||||
|
if got := AtMost(uint8(10), uint8(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint8(3), uint8(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint8(5), uint8(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint16", func(t *testing.T) {
|
||||||
|
if got := AtMost(uint16(10), uint16(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint16(3), uint16(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint16(5), uint16(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint32", func(t *testing.T) {
|
||||||
|
if got := AtMost(uint32(10), uint32(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint32(3), uint32(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint32(5), uint32(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint64", func(t *testing.T) {
|
||||||
|
if got := AtMost(uint64(10), uint64(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint64(3), uint64(5)); got != 3 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(uint64(5), uint64(5)); got != 5 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float32", func(t *testing.T) {
|
||||||
|
if got := AtMost(float32(10.0), float32(5.0)); got != 5.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(float32(3.0), float32(5.0)); got != 3.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3.0", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(float32(5.0), float32(5.0)); got != 5.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float64", func(t *testing.T) {
|
||||||
|
if got := AtMost(10.0, 5.0); got != 5.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(3.0, 5.0); got != 3.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 3.0", got)
|
||||||
|
}
|
||||||
|
if got := AtMost(5.0, 5.0); got != 5.0 {
|
||||||
|
t.Errorf("AtMost() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBetween(t *testing.T) {
|
||||||
|
t.Run("test int", func(t *testing.T) {
|
||||||
|
if got := Between(10, 5, 15); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(3, 5, 15); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(20, 5, 15); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(5, 5, 15); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(15, 5, 15); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int8", func(t *testing.T) {
|
||||||
|
if got := Between(int8(10), int8(5), int8(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(int8(3), int8(5), int8(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int8(20), int8(5), int8(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(int8(5), int8(5), int8(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int8(15), int8(5), int8(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int16", func(t *testing.T) {
|
||||||
|
if got := Between(int16(10), int16(5), int16(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(int16(3), int16(5), int16(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int16(20), int16(5), int16(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(int16(5), int16(5), int16(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int16(15), int16(5), int16(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int32", func(t *testing.T) {
|
||||||
|
if got := Between(int32(10), int32(5), int32(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(int32(3), int32(5), int32(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int32(20), int32(5), int32(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(int32(5), int32(5), int32(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int32(15), int32(5), int32(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test int64", func(t *testing.T) {
|
||||||
|
if got := Between(int64(10), int64(5), int64(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(int64(3), int64(5), int64(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int64(20), int64(5), int64(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(int64(5), int64(5), int64(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(int64(15), int64(5), int64(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint", func(t *testing.T) {
|
||||||
|
if got := Between(uint(10), uint(5), uint(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint(3), uint(5), uint(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint(20), uint(5), uint(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint(5), uint(5), uint(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint(15), uint(5), uint(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint8", func(t *testing.T) {
|
||||||
|
if got := Between(uint8(10), uint8(5), uint8(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint8(3), uint8(5), uint8(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint8(20), uint8(5), uint8(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint8(5), uint8(5), uint8(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint8(15), uint8(5), uint8(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint16", func(t *testing.T) {
|
||||||
|
if got := Between(uint16(10), uint16(5), uint16(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint16(3), uint16(5), uint16(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint16(20), uint16(5), uint16(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint16(5), uint16(5), uint16(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint16(15), uint16(5), uint16(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint32", func(t *testing.T) {
|
||||||
|
if got := Between(uint32(10), uint32(5), uint32(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint32(3), uint32(5), uint32(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint32(20), uint32(5), uint32(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint32(5), uint32(5), uint32(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint32(15), uint32(5), uint32(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test uint64", func(t *testing.T) {
|
||||||
|
if got := Between(uint64(10), uint64(5), uint64(15)); got != 10 {
|
||||||
|
t.Errorf("Between() = %v, want 10", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint64(3), uint64(5), uint64(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint64(20), uint64(5), uint64(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint64(5), uint64(5), uint64(15)); got != 5 {
|
||||||
|
t.Errorf("Between() = %v, want 5", got)
|
||||||
|
}
|
||||||
|
if got := Between(uint64(15), uint64(5), uint64(15)); got != 15 {
|
||||||
|
t.Errorf("Between() = %v, want 15", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float32", func(t *testing.T) {
|
||||||
|
if got := Between(float32(10.0), float32(5.0), float32(15.0)); got != 10.0 {
|
||||||
|
t.Errorf("Between() = %v, want 10.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(float32(3.0), float32(5.0), float32(15.0)); got != 5.0 {
|
||||||
|
t.Errorf("Between() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(float32(20.0), float32(5.0), float32(15.0)); got != 15.0 {
|
||||||
|
t.Errorf("Between() = %v, want 15.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(float32(5.0), float32(5.0), float32(15.0)); got != 5.0 {
|
||||||
|
t.Errorf("Between() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(float32(15.0), float32(5.0), float32(15.0)); got != 15.0 {
|
||||||
|
t.Errorf("Between() = %v, want 15.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float64", func(t *testing.T) {
|
||||||
|
if got := Between(10.0, 5.0, 15.0); got != 10.0 {
|
||||||
|
t.Errorf("Between() = %v, want 10.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(3.0, 5.0, 15.0); got != 5.0 {
|
||||||
|
t.Errorf("Between() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(20.0, 5.0, 15.0); got != 15.0 {
|
||||||
|
t.Errorf("Between() = %v, want 15.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(5.0, 5.0, 15.0); got != 5.0 {
|
||||||
|
t.Errorf("Between() = %v, want 5.0", got)
|
||||||
|
}
|
||||||
|
if got := Between(15.0, 5.0, 15.0); got != 15.0 {
|
||||||
|
t.Errorf("Between() = %v, want 15.0", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func FuzzMapReduce(f *testing.F) {
|
func FuzzMapReduce(f *testing.F) {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
f.Add(int64(10), runtime.NumCPU())
|
f.Add(int64(10), runtime.NumCPU())
|
||||||
f.Fuzz(func(t *testing.T, n int64, workers int) {
|
f.Fuzz(func(t *testing.T, n int64, workers int) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
// If Fuzz stuck, we don't know why, because it only returns hung or unexpected,
|
// If Fuzz stuck, we don't know why, because it only returns hung or unexpected,
|
||||||
// so we need to simulate the fuzz test in test mode.
|
// so we need to simulate the fuzz test in test mode.
|
||||||
func TestMapReduceRandom(t *testing.T) {
|
func TestMapReduceRandom(t *testing.T) {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
const (
|
const (
|
||||||
times = 10000
|
times = 10000
|
||||||
|
|||||||
@@ -23,42 +23,17 @@ var (
|
|||||||
preTotal uint64
|
preTotal uint64
|
||||||
limit float64
|
limit float64
|
||||||
cores uint64
|
cores uint64
|
||||||
|
noCgroup bool
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// if /proc not present, ignore the cpu calculation, like wsl linux
|
|
||||||
func initialize() {
|
|
||||||
cpus, err := effectiveCpus()
|
|
||||||
if err != nil {
|
|
||||||
logx.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cores = uint64(cpus)
|
|
||||||
limit = float64(cpus)
|
|
||||||
quota, err := cpuQuota()
|
|
||||||
if err == nil && quota > 0 {
|
|
||||||
if quota < limit {
|
|
||||||
limit = quota
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preSystem, err = systemCpuUsage()
|
|
||||||
if err != nil {
|
|
||||||
logx.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
preTotal, err = cpuUsage()
|
|
||||||
if err != nil {
|
|
||||||
logx.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshCpu refreshes cpu usage and returns.
|
// RefreshCpu refreshes cpu usage and returns.
|
||||||
func RefreshCpu() uint64 {
|
func RefreshCpu() uint64 {
|
||||||
initOnce.Do(initialize)
|
initializeOnce()
|
||||||
|
|
||||||
|
if noCgroup {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
total, err := cpuUsage()
|
total, err := cpuUsage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,6 +87,47 @@ func effectiveCpus() (int, error) {
|
|||||||
return cg.effectiveCpus()
|
return cg.effectiveCpus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if /proc not present, ignore the cpu calculation, like wsl linux
|
||||||
|
func initialize() error {
|
||||||
|
cpus, err := effectiveCpus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cores = uint64(cpus)
|
||||||
|
limit = float64(cpus)
|
||||||
|
quota, err := cpuQuota()
|
||||||
|
if err == nil && quota > 0 {
|
||||||
|
if quota < limit {
|
||||||
|
limit = quota
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preSystem, err = systemCpuUsage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
preTotal, err = cpuUsage()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeOnce() {
|
||||||
|
initOnce.Do(func() {
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
noCgroup = true
|
||||||
|
logx.Error(p)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := initialize(); err != nil {
|
||||||
|
noCgroup = true
|
||||||
|
logx.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func systemCpuUsage() (uint64, error) {
|
func systemCpuUsage() (uint64, error) {
|
||||||
lines, err := iox.ReadTextLines(statFile, iox.WithoutBlank())
|
lines, err := iox.ReadTextLines(statFile, iox.WithoutBlank())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package stat
|
package stat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetrics(t *testing.T) {
|
func TestMetrics(t *testing.T) {
|
||||||
@@ -30,6 +32,34 @@ func TestMetrics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTopDurationWithEmpty(t *testing.T) {
|
||||||
|
assert.Equal(t, float32(0), getTopDuration(nil))
|
||||||
|
assert.Equal(t, float32(0), getTopDuration([]Task{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogAndReport(t *testing.T) {
|
||||||
|
buf := logtest.NewCollector(t)
|
||||||
|
old := logEnabled.True()
|
||||||
|
logEnabled.Set(true)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
logEnabled.Set(old)
|
||||||
|
})
|
||||||
|
|
||||||
|
log(&StatReport{})
|
||||||
|
assert.NotEmpty(t, buf.String())
|
||||||
|
|
||||||
|
writerLock.Lock()
|
||||||
|
writer := reportWriter
|
||||||
|
writerLock.Unlock()
|
||||||
|
buf = logtest.NewCollector(t)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
SetReportWriter(writer)
|
||||||
|
})
|
||||||
|
SetReportWriter(&badWriter{})
|
||||||
|
writeReport(&StatReport{})
|
||||||
|
assert.NotEmpty(t, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
type mockedWriter struct {
|
type mockedWriter struct {
|
||||||
report *StatReport
|
report *StatReport
|
||||||
}
|
}
|
||||||
@@ -38,3 +68,9 @@ func (m *mockedWriter) Write(report *StatReport) error {
|
|||||||
m.report = report
|
m.report = report
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type badWriter struct{}
|
||||||
|
|
||||||
|
func (b *badWriter) Write(report *StatReport) error {
|
||||||
|
return errors.New("bad")
|
||||||
|
}
|
||||||
|
|||||||
@@ -603,11 +603,11 @@ func (d *dropBreaker) DoWithAcceptable(_ func() error, _ breaker.Acceptable) err
|
|||||||
return errDummy
|
return errDummy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dropBreaker) DoWithFallback(_ func() error, _ func(err error) error) error {
|
func (d *dropBreaker) DoWithFallback(_ func() error, _ breaker.Fallback) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dropBreaker) DoWithFallbackAcceptable(_ func() error, _ func(err error) error,
|
func (d *dropBreaker) DoWithFallbackAcceptable(_ func() error, _ breaker.Fallback,
|
||||||
_ breaker.Acceptable) error {
|
_ breaker.Acceptable) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package mon
|
package mon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/syncx"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +19,17 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
options = mopt.ClientOptions
|
|
||||||
|
|
||||||
// Option defines the method to customize a mongo model.
|
// Option defines the method to customize a mongo model.
|
||||||
Option func(opts *options)
|
Option func(opts *options)
|
||||||
|
|
||||||
|
// TypeCodec is a struct that stores specific type Encoder/Decoder.
|
||||||
|
TypeCodec struct {
|
||||||
|
ValueType reflect.Type
|
||||||
|
Encoder bsoncodec.ValueEncoder
|
||||||
|
Decoder bsoncodec.ValueDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
options = mopt.ClientOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
// DisableLog disables logging of mongo commands, includes info and slow logs.
|
// DisableLog disables logging of mongo commands, includes info and slow logs.
|
||||||
@@ -38,15 +48,27 @@ func SetSlowThreshold(threshold time.Duration) {
|
|||||||
slowThreshold.Set(threshold)
|
slowThreshold.Set(threshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultTimeoutOption() Option {
|
|
||||||
return func(opts *options) {
|
|
||||||
opts.SetTimeout(defaultTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout set the mon client operation timeout.
|
// WithTimeout set the mon client operation timeout.
|
||||||
func WithTimeout(timeout time.Duration) Option {
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
return func(opts *options) {
|
return func(opts *options) {
|
||||||
opts.SetTimeout(timeout)
|
opts.SetTimeout(timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTypeCodec registers TypeCodecs to convert custom types.
|
||||||
|
func WithTypeCodec(typeCodecs ...TypeCodec) Option {
|
||||||
|
return func(opts *options) {
|
||||||
|
registry := bson.NewRegistry()
|
||||||
|
for _, v := range typeCodecs {
|
||||||
|
registry.RegisterTypeEncoder(v.ValueType, v.Encoder)
|
||||||
|
registry.RegisterTypeDecoder(v.ValueType, v.Decoder)
|
||||||
|
}
|
||||||
|
opts.SetRegistry(registry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTimeoutOption() Option {
|
||||||
|
return func(opts *options) {
|
||||||
|
opts.SetTimeout(defaultTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package mon
|
package mon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,3 +55,56 @@ func TestDisableInfoLog(t *testing.T) {
|
|||||||
assert.False(t, logMon.True())
|
assert.False(t, logMon.True())
|
||||||
assert.True(t, logSlowMon.True())
|
assert.True(t, logSlowMon.True())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithRegistryForTimestampRegisterType(t *testing.T) {
|
||||||
|
opts := mopt.Client()
|
||||||
|
|
||||||
|
// mongoDateTimeEncoder allow user convert time.Time to primitive.DateTime.
|
||||||
|
var mongoDateTimeEncoder bsoncodec.ValueEncoderFunc = func(ect bsoncodec.EncodeContext, w bsonrw.ValueWriter, value reflect.Value) error {
|
||||||
|
// Use reflect, determine if it can be converted to time.Time.
|
||||||
|
dec, ok := value.Interface().(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("value %v to encode is not of type time.Time", value)
|
||||||
|
}
|
||||||
|
return w.WriteDateTime(dec.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// mongoDateTimeEncoder allow user convert primitive.DateTime to time.Time.
|
||||||
|
var mongoDateTimeDecoder bsoncodec.ValueDecoderFunc = func(ect bsoncodec.DecodeContext, r bsonrw.ValueReader, value reflect.Value) error {
|
||||||
|
primTime, err := r.ReadDateTime()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading primitive.DateTime from ValueReader: %v", err)
|
||||||
|
}
|
||||||
|
value.Set(reflect.ValueOf(time.Unix(primTime, 0)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
codecs := []TypeCodec{
|
||||||
|
{
|
||||||
|
ValueType: reflect.TypeOf(time.Time{}),
|
||||||
|
Encoder: mongoDateTimeEncoder,
|
||||||
|
Decoder: mongoDateTimeDecoder,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
WithTypeCodec(codecs...)(opts)
|
||||||
|
|
||||||
|
for _, v := range codecs {
|
||||||
|
// Validate Encoder
|
||||||
|
enc, err := opts.Registry.LookupEncoder(v.ValueType)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if assert.ObjectsAreEqual(v.Encoder, enc) {
|
||||||
|
t.Errorf("Encoder got from Registry: %v, but want: %v", enc, v.Encoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Decoder
|
||||||
|
dec, err := opts.Registry.LookupDecoder(v.ValueType)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if assert.ObjectsAreEqual(v.Decoder, dec) {
|
||||||
|
t.Errorf("Decoder got from Registry: %v, but want: %v", dec, v.Decoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -483,13 +483,8 @@ func (s *Redis) ExistsManyCtx(ctx context.Context, keys ...string) (val int64, e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.Exists(ctx, keys...).Result()
|
val, err = conn.Exists(ctx, keys...).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -543,13 +538,8 @@ func (s *Redis) GeoAddCtx(ctx context.Context, key string, geoLocation ...*GeoLo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoAdd(ctx, key, geoLocation...).Result()
|
val, err = conn.GeoAdd(ctx, key, geoLocation...).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -569,13 +559,8 @@ func (s *Redis) GeoDistCtx(ctx context.Context, key, member1, member2, unit stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoDist(ctx, key, member1, member2, unit).Result()
|
val, err = conn.GeoDist(ctx, key, member1, member2, unit).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -595,13 +580,8 @@ func (s *Redis) GeoHashCtx(ctx context.Context, key string, members ...string) (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoHash(ctx, key, members...).Result()
|
val, err = conn.GeoHash(ctx, key, members...).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -622,13 +602,8 @@ func (s *Redis) GeoRadiusCtx(ctx context.Context, key string, longitude, latitud
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoRadius(ctx, key, longitude, latitude, query).Result()
|
val, err = conn.GeoRadius(ctx, key, longitude, latitude, query).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -648,13 +623,8 @@ func (s *Redis) GeoRadiusByMemberCtx(ctx context.Context, key, member string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoRadiusByMember(ctx, key, member, query).Result()
|
val, err = conn.GeoRadiusByMember(ctx, key, member, query).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -674,13 +644,8 @@ func (s *Redis) GeoPosCtx(ctx context.Context, key string, members ...string) (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.GeoPos(ctx, key, members...).Result()
|
val, err = conn.GeoPos(ctx, key, members...).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -699,7 +664,7 @@ func (s *Redis) GetCtx(ctx context.Context, key string) (val string, err error)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, err = conn.Get(ctx, key).Result(); err == red.Nil {
|
if val, err = conn.Get(ctx, key).Result(); errors.Is(err, red.Nil) {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -749,7 +714,7 @@ func (s *Redis) GetSetCtx(ctx context.Context, key, value string) (val string, e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, err = conn.GetSet(ctx, key, value).Result(); err == red.Nil {
|
if val, err = conn.GetSet(ctx, key, value).Result(); errors.Is(err, red.Nil) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,17 +840,16 @@ func (s *Redis) HincrbyFloat(key, field string, increment float64) (float64, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HincrbyFloatCtx is the implementation of redis hincrbyfloat command.
|
// HincrbyFloatCtx is the implementation of redis hincrbyfloat command.
|
||||||
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (val float64, err error) {
|
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (
|
||||||
|
val float64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err = conn.HIncrByFloat(ctx, key, field, increment).Result()
|
val, err = conn.HIncrByFloat(ctx, key, field, increment).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1339,6 +1303,26 @@ func (s *Redis) MgetCtx(ctx context.Context, keys ...string) (val []string, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mset is the implementation of redis mset command.
|
||||||
|
func (s *Redis) Mset(fieldsAndValues ...any) (string, error) {
|
||||||
|
return s.MsetCtx(context.Background(), fieldsAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsetCtx is the implementation of redis mset command.
|
||||||
|
func (s *Redis) MsetCtx(ctx context.Context, fieldsAndValues ...any) (val string, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.MSet(ctx, fieldsAndValues...).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Persist is the implementation of redis persist command.
|
// Persist is the implementation of redis persist command.
|
||||||
func (s *Redis) Persist(key string) (bool, error) {
|
func (s *Redis) Persist(key string) (bool, error) {
|
||||||
return s.PersistCtx(context.Background(), key)
|
return s.PersistCtx(context.Background(), key)
|
||||||
@@ -2028,6 +2012,7 @@ func (s *Redis) TtlCtx(ctx context.Context, key string) (val int, err error) {
|
|||||||
// -1 means key exists but has no expire
|
// -1 means key exists but has no expire
|
||||||
val = int(duration)
|
val = int(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
@@ -2074,6 +2059,46 @@ func (s *Redis) ZaddFloatCtx(ctx context.Context, key string, score float64, val
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zaddnx is the implementation of redis zadd nx command.
|
||||||
|
func (s *Redis) Zaddnx(key string, score int64, value string) (val bool, err error) {
|
||||||
|
return s.ZaddnxCtx(context.Background(), key, score, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZaddnxCtx is the implementation of redis zadd nx command.
|
||||||
|
func (s *Redis) ZaddnxCtx(ctx context.Context, key string, score int64, value string) (
|
||||||
|
val bool, err error) {
|
||||||
|
return s.ZaddnxFloatCtx(ctx, key, float64(score), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZaddnxFloat is the implementation of redis zaddnx command.
|
||||||
|
func (s *Redis) ZaddnxFloat(key string, score float64, value string) (bool, error) {
|
||||||
|
return s.ZaddFloatCtx(context.Background(), key, score, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZaddnxFloatCtx is the implementation of redis zaddnx command.
|
||||||
|
func (s *Redis) ZaddnxFloatCtx(ctx context.Context, key string, score float64, value string) (
|
||||||
|
val bool, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := conn.ZAddNX(ctx, key, red.Z{
|
||||||
|
Score: score,
|
||||||
|
Member: value,
|
||||||
|
}).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val = v == 1
|
||||||
|
return nil
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Zadds is the implementation of redis zadds command.
|
// Zadds is the implementation of redis zadds command.
|
||||||
func (s *Redis) Zadds(key string, ps ...Pair) (int64, error) {
|
func (s *Redis) Zadds(key string, ps ...Pair) (int64, error) {
|
||||||
return s.ZaddsCtx(context.Background(), key, ps...)
|
return s.ZaddsCtx(context.Background(), key, ps...)
|
||||||
@@ -2093,13 +2118,8 @@ func (s *Redis) ZaddsCtx(ctx context.Context, key string, ps ...Pair) (val int64
|
|||||||
zs = append(zs, z)
|
zs = append(zs, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := conn.ZAdd(ctx, key, zs...).Result()
|
val, err = conn.ZAdd(ctx, key, zs...).Result()
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
val = v
|
|
||||||
return nil
|
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -2219,6 +2239,7 @@ func (s *Redis) ZscoreByFloatCtx(ctx context.Context, key, value string) (val fl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err = conn.ZScore(ctx, key, value).Result()
|
val, err = conn.ZScore(ctx, key, value).Result()
|
||||||
return err
|
return err
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
@@ -2883,7 +2904,7 @@ func withHook(hook red.Hook) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func acceptable(err error) bool {
|
func acceptable(err error) bool {
|
||||||
return err == nil || err == red.Nil || errors.Is(err, context.Canceled)
|
return err == nil || errors.Is(err, red.Nil) || errors.Is(err, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedis(r *Redis) (RedisNode, error) {
|
func getRedis(r *Redis) (RedisNode, error) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ type RedisLock struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.NewSource(time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisLock returns a RedisLock.
|
// NewRedisLock returns a RedisLock.
|
||||||
|
|||||||
@@ -42,21 +42,6 @@ type (
|
|||||||
// SqlOption defines the method to customize a sql connection.
|
// SqlOption defines the method to customize a sql connection.
|
||||||
SqlOption func(*commonSqlConn)
|
SqlOption func(*commonSqlConn)
|
||||||
|
|
||||||
// StmtSession interface represents a session that can be used to execute statements.
|
|
||||||
StmtSession interface {
|
|
||||||
Close() error
|
|
||||||
Exec(args ...any) (sql.Result, error)
|
|
||||||
ExecCtx(ctx context.Context, args ...any) (sql.Result, error)
|
|
||||||
QueryRow(v any, args ...any) error
|
|
||||||
QueryRowCtx(ctx context.Context, v any, args ...any) error
|
|
||||||
QueryRowPartial(v any, args ...any) error
|
|
||||||
QueryRowPartialCtx(ctx context.Context, v any, args ...any) error
|
|
||||||
QueryRows(v any, args ...any) error
|
|
||||||
QueryRowsCtx(ctx context.Context, v any, args ...any) error
|
|
||||||
QueryRowsPartial(v any, args ...any) error
|
|
||||||
QueryRowsPartialCtx(ctx context.Context, v any, args ...any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
// Because CORBA doesn't support PREPARE, so we need to combine the
|
// Because CORBA doesn't support PREPARE, so we need to combine the
|
||||||
// query arguments into one string and do underlying query without arguments
|
// query arguments into one string and do underlying query without arguments
|
||||||
@@ -65,7 +50,7 @@ type (
|
|||||||
onError func(context.Context, error)
|
onError func(context.Context, error)
|
||||||
beginTx beginnable
|
beginTx beginnable
|
||||||
brk breaker.Breaker
|
brk breaker.Breaker
|
||||||
accept func(error) bool
|
accept breaker.Acceptable
|
||||||
}
|
}
|
||||||
|
|
||||||
connProvider func() (*sql.DB, error)
|
connProvider func() (*sql.DB, error)
|
||||||
@@ -76,18 +61,6 @@ type (
|
|||||||
Query(query string, args ...any) (*sql.Rows, error)
|
Query(query string, args ...any) (*sql.Rows, error)
|
||||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
statement struct {
|
|
||||||
query string
|
|
||||||
stmt *sql.Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
stmtConn interface {
|
|
||||||
Exec(args ...any) (sql.Result, error)
|
|
||||||
ExecContext(ctx context.Context, args ...any) (sql.Result, error)
|
|
||||||
Query(args ...any) (*sql.Rows, error)
|
|
||||||
QueryContext(ctx context.Context, args ...any) (*sql.Rows, error)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSqlConn returns a SqlConn with given driver name and datasource.
|
// NewSqlConn returns a SqlConn with given driver name and datasource.
|
||||||
@@ -189,8 +162,10 @@ func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt Stm
|
|||||||
}
|
}
|
||||||
|
|
||||||
stmt = statement{
|
stmt = statement{
|
||||||
query: query,
|
query: query,
|
||||||
stmt: st,
|
stmt: st,
|
||||||
|
brk: db.brk,
|
||||||
|
accept: db.acceptable,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, db.acceptable)
|
}, db.acceptable)
|
||||||
@@ -311,7 +286,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 qerr error
|
var scanFailed bool
|
||||||
err = db.brk.DoWithAcceptable(func() error {
|
err = db.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := db.connProv()
|
conn, err := db.connProv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -320,11 +295,14 @@ 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 {
|
||||||
qerr = scanner(rows)
|
e := scanner(rows)
|
||||||
return qerr
|
if e != nil {
|
||||||
|
scanFailed = true
|
||||||
|
}
|
||||||
|
return e
|
||||||
}, q, args...)
|
}, q, args...)
|
||||||
}, func(err error) bool {
|
}, func(err error) bool {
|
||||||
return errors.Is(err, qerr) || db.acceptable(err)
|
return scanFailed || db.acceptable(err)
|
||||||
})
|
})
|
||||||
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
metricReqErr.Inc("queryRows", "breaker")
|
metricReqErr.Inc("queryRows", "breaker")
|
||||||
@@ -333,83 +311,6 @@ func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s statement) Close() error {
|
|
||||||
return s.stmt.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) Exec(args ...any) (sql.Result, error) {
|
|
||||||
return s.ExecCtx(context.Background(), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) ExecCtx(ctx context.Context, args ...any) (result sql.Result, err error) {
|
|
||||||
ctx, span := startSpan(ctx, "Exec")
|
|
||||||
defer func() {
|
|
||||||
endSpan(span, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return execStmt(ctx, s.stmt, s.query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRow(v any, args ...any) error {
|
|
||||||
return s.QueryRowCtx(context.Background(), v, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowCtx(ctx context.Context, v any, args ...any) (err error) {
|
|
||||||
ctx, span := startSpan(ctx, "QueryRow")
|
|
||||||
defer func() {
|
|
||||||
endSpan(span, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
|
||||||
return unmarshalRow(v, rows, true)
|
|
||||||
}, s.query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowPartial(v any, args ...any) error {
|
|
||||||
return s.QueryRowPartialCtx(context.Background(), v, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowPartialCtx(ctx context.Context, v any, args ...any) (err error) {
|
|
||||||
ctx, span := startSpan(ctx, "QueryRowPartial")
|
|
||||||
defer func() {
|
|
||||||
endSpan(span, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
|
||||||
return unmarshalRow(v, rows, false)
|
|
||||||
}, s.query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRows(v any, args ...any) error {
|
|
||||||
return s.QueryRowsCtx(context.Background(), v, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowsCtx(ctx context.Context, v any, args ...any) (err error) {
|
|
||||||
ctx, span := startSpan(ctx, "QueryRows")
|
|
||||||
defer func() {
|
|
||||||
endSpan(span, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
|
||||||
return unmarshalRows(v, rows, true)
|
|
||||||
}, s.query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowsPartial(v any, args ...any) error {
|
|
||||||
return s.QueryRowsPartialCtx(context.Background(), v, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statement) QueryRowsPartialCtx(ctx context.Context, v any, args ...any) (err error) {
|
|
||||||
ctx, span := startSpan(ctx, "QueryRowsPartial")
|
|
||||||
defer func() {
|
|
||||||
endSpan(span, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
|
||||||
return unmarshalRows(v, rows, false)
|
|
||||||
}, s.query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAcceptable returns a SqlOption that setting the acceptable function.
|
// WithAcceptable returns a SqlOption that setting the acceptable function.
|
||||||
// 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 {
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ func TestStatement(t *testing.T) {
|
|||||||
st := statement{
|
st := statement{
|
||||||
query: "foo",
|
query: "foo",
|
||||||
stmt: stmt,
|
stmt: stmt,
|
||||||
|
brk: breaker.NopBreaker(),
|
||||||
}
|
}
|
||||||
assert.NoError(t, st.Close())
|
assert.NoError(t, st.Close())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package sqlx
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/syncx"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
"github.com/zeromicro/go-zero/core/timex"
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
@@ -18,6 +20,145 @@ var (
|
|||||||
logSlowSql = syncx.ForAtomicBool(true)
|
logSlowSql = syncx.ForAtomicBool(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// StmtSession interface represents a session that can be used to execute statements.
|
||||||
|
StmtSession interface {
|
||||||
|
Close() error
|
||||||
|
Exec(args ...any) (sql.Result, error)
|
||||||
|
ExecCtx(ctx context.Context, args ...any) (sql.Result, error)
|
||||||
|
QueryRow(v any, args ...any) error
|
||||||
|
QueryRowCtx(ctx context.Context, v any, args ...any) error
|
||||||
|
QueryRowPartial(v any, args ...any) error
|
||||||
|
QueryRowPartialCtx(ctx context.Context, v any, args ...any) error
|
||||||
|
QueryRows(v any, args ...any) error
|
||||||
|
QueryRowsCtx(ctx context.Context, v any, args ...any) error
|
||||||
|
QueryRowsPartial(v any, args ...any) error
|
||||||
|
QueryRowsPartialCtx(ctx context.Context, v any, args ...any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
statement struct {
|
||||||
|
query string
|
||||||
|
stmt *sql.Stmt
|
||||||
|
brk breaker.Breaker
|
||||||
|
accept breaker.Acceptable
|
||||||
|
}
|
||||||
|
|
||||||
|
stmtConn interface {
|
||||||
|
Exec(args ...any) (sql.Result, error)
|
||||||
|
ExecContext(ctx context.Context, args ...any) (sql.Result, error)
|
||||||
|
Query(args ...any) (*sql.Rows, error)
|
||||||
|
QueryContext(ctx context.Context, args ...any) (*sql.Rows, error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s statement) Close() error {
|
||||||
|
return s.stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) Exec(args ...any) (sql.Result, error) {
|
||||||
|
return s.ExecCtx(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) ExecCtx(ctx context.Context, args ...any) (result sql.Result, err error) {
|
||||||
|
ctx, span := startSpan(ctx, "Exec")
|
||||||
|
defer func() {
|
||||||
|
endSpan(span, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
result, err = execStmt(ctx, s.stmt, s.query, args...)
|
||||||
|
return err
|
||||||
|
}, func(err error) bool {
|
||||||
|
return s.accept(err)
|
||||||
|
})
|
||||||
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
|
metricReqErr.Inc("stmt_exec", "breaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRow(v any, args ...any) error {
|
||||||
|
return s.QueryRowCtx(context.Background(), v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowCtx(ctx context.Context, v any, args ...any) (err error) {
|
||||||
|
ctx, span := startSpan(ctx, "QueryRow")
|
||||||
|
defer func() {
|
||||||
|
endSpan(span, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s.queryRows(ctx, func(v any, scanner rowsScanner) error {
|
||||||
|
return unmarshalRow(v, scanner, true)
|
||||||
|
}, v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowPartial(v any, args ...any) error {
|
||||||
|
return s.QueryRowPartialCtx(context.Background(), v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowPartialCtx(ctx context.Context, v any, args ...any) (err error) {
|
||||||
|
ctx, span := startSpan(ctx, "QueryRowPartial")
|
||||||
|
defer func() {
|
||||||
|
endSpan(span, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s.queryRows(ctx, func(v any, scanner rowsScanner) error {
|
||||||
|
return unmarshalRow(v, scanner, false)
|
||||||
|
}, v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRows(v any, args ...any) error {
|
||||||
|
return s.QueryRowsCtx(context.Background(), v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowsCtx(ctx context.Context, v any, args ...any) (err error) {
|
||||||
|
ctx, span := startSpan(ctx, "QueryRows")
|
||||||
|
defer func() {
|
||||||
|
endSpan(span, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s.queryRows(ctx, func(v any, scanner rowsScanner) error {
|
||||||
|
return unmarshalRows(v, scanner, true)
|
||||||
|
}, v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowsPartial(v any, args ...any) error {
|
||||||
|
return s.QueryRowsPartialCtx(context.Background(), v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) QueryRowsPartialCtx(ctx context.Context, v any, args ...any) (err error) {
|
||||||
|
ctx, span := startSpan(ctx, "QueryRowsPartial")
|
||||||
|
defer func() {
|
||||||
|
endSpan(span, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s.queryRows(ctx, func(v any, scanner rowsScanner) error {
|
||||||
|
return unmarshalRows(v, scanner, false)
|
||||||
|
}, v, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s statement) queryRows(ctx context.Context, scanFn func(any, rowsScanner) error,
|
||||||
|
v any, args ...any) error {
|
||||||
|
var scanFailed bool
|
||||||
|
err := s.brk.DoWithAcceptable(func() error {
|
||||||
|
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
|
||||||
|
err := scanFn(v, rows)
|
||||||
|
if err != nil {
|
||||||
|
scanFailed = true
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}, s.query, args...)
|
||||||
|
}, func(err error) bool {
|
||||||
|
return scanFailed || s.accept(err)
|
||||||
|
})
|
||||||
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
|
metricReqErr.Inc("stmt_queryRows", "breaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// DisableLog disables logging of sql statements, includes info and slow logs.
|
// DisableLog disables logging of sql statements, includes info and slow logs.
|
||||||
func DisableLog() {
|
func DisableLog() {
|
||||||
logSql.Set(false)
|
logSql.Set(false)
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/dbtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errMockedPlaceholder = errors.New("placeholder")
|
var errMockedPlaceholder = errors.New("placeholder")
|
||||||
@@ -219,6 +222,74 @@ func TestNilGuard(t *testing.T) {
|
|||||||
assert.Equal(t, nilGuard{}, guard)
|
assert.Equal(t, nilGuard{}, guard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStmtBreaker(t *testing.T) {
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
mock.ExpectPrepare("any")
|
||||||
|
|
||||||
|
conn := NewSqlConnFromDB(db)
|
||||||
|
stmt, err := conn.Prepare("any")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var val struct {
|
||||||
|
Foo int
|
||||||
|
Bar string
|
||||||
|
}
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
row := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||||
|
mock.ExpectQuery("any").WillReturnRows(row)
|
||||||
|
err := stmt.QueryRow(&val)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.NotErrorIs(t, err, breaker.ErrServiceUnavailable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
mock.ExpectPrepare("any")
|
||||||
|
conn := NewSqlConnFromDB(db)
|
||||||
|
stmt, err := conn.Prepare("any")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
assert.Error(t, conn.Transact(func(session Session) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakerTriggered bool
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
_, err = stmt.Exec("any")
|
||||||
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
|
breakerTriggered = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, breakerTriggered)
|
||||||
|
})
|
||||||
|
|
||||||
|
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
mock.ExpectPrepare("any")
|
||||||
|
conn := NewSqlConnFromDB(db)
|
||||||
|
stmt, err := conn.Prepare("any")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
assert.Error(t, conn.Transact(func(session Session) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakerTriggered bool
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
err = stmt.QueryRows(&struct{}{}, "any")
|
||||||
|
if errors.Is(err, breaker.ErrServiceUnavailable) {
|
||||||
|
breakerTriggered = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, breakerTriggered)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type mockedSessionConn struct {
|
type mockedSessionConn struct {
|
||||||
lastInsertId int64
|
lastInsertId int64
|
||||||
rowsAffected int64
|
rowsAffected int64
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -75,6 +77,7 @@ func (t txSession) PrepareCtx(ctx context.Context, q string) (stmtSession StmtSe
|
|||||||
return statement{
|
return statement{
|
||||||
query: q,
|
query: q,
|
||||||
stmt: stmt,
|
stmt: stmt,
|
||||||
|
brk: breaker.NopBreaker(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func FuzzNodeFind(f *testing.F) {
|
func FuzzNodeFind(f *testing.F) {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
f.Add(10)
|
f.Add(10)
|
||||||
f.Fuzz(func(t *testing.T, keys int) {
|
f.Fuzz(func(t *testing.T, keys int) {
|
||||||
|
|||||||
105
core/threading/stablerunner.go
Normal file
105
core/threading/stablerunner.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package threading
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const factor = 10
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRunnerClosed = errors.New("runner closed")
|
||||||
|
|
||||||
|
bufSize = runtime.NumCPU() * factor
|
||||||
|
)
|
||||||
|
|
||||||
|
// StableRunner is a runner that guarantees messages are taken out with the pushed order.
|
||||||
|
// This runner is typically useful for Kafka consumers with parallel processing.
|
||||||
|
type StableRunner[I, O any] struct {
|
||||||
|
handle func(I) O
|
||||||
|
consumedIndex uint64
|
||||||
|
writtenIndex uint64
|
||||||
|
ring []*struct {
|
||||||
|
value chan O
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
runner *TaskRunner
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStableRunner returns a new StableRunner with given message processor fn.
|
||||||
|
func NewStableRunner[I, O any](fn func(I) O) *StableRunner[I, O] {
|
||||||
|
ring := make([]*struct {
|
||||||
|
value chan O
|
||||||
|
lock sync.Mutex
|
||||||
|
}, bufSize)
|
||||||
|
for i := 0; i < bufSize; i++ {
|
||||||
|
ring[i] = &struct {
|
||||||
|
value chan O
|
||||||
|
lock sync.Mutex
|
||||||
|
}{
|
||||||
|
value: make(chan O, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StableRunner[I, O]{
|
||||||
|
handle: fn,
|
||||||
|
ring: ring,
|
||||||
|
runner: NewTaskRunner(runtime.NumCPU()),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the next processed message in order.
|
||||||
|
// This method should be called in one goroutine.
|
||||||
|
func (r *StableRunner[I, O]) Get() (O, error) {
|
||||||
|
defer atomic.AddUint64(&r.consumedIndex, 1)
|
||||||
|
|
||||||
|
index := atomic.LoadUint64(&r.consumedIndex)
|
||||||
|
offset := index % uint64(bufSize)
|
||||||
|
holder := r.ring[offset]
|
||||||
|
|
||||||
|
select {
|
||||||
|
case o := <-holder.value:
|
||||||
|
return o, nil
|
||||||
|
case <-r.done:
|
||||||
|
if atomic.LoadUint64(&r.consumedIndex) < atomic.LoadUint64(&r.writtenIndex) {
|
||||||
|
return <-holder.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var o O
|
||||||
|
return o, ErrRunnerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push pushes the message v into the runner and to be processed concurrently,
|
||||||
|
// after processed, it will be cached to let caller take it in pushing order.
|
||||||
|
func (r *StableRunner[I, O]) Push(v I) error {
|
||||||
|
select {
|
||||||
|
case <-r.done:
|
||||||
|
return ErrRunnerClosed
|
||||||
|
default:
|
||||||
|
index := atomic.AddUint64(&r.writtenIndex, 1)
|
||||||
|
offset := (index - 1) % uint64(bufSize)
|
||||||
|
holder := r.ring[offset]
|
||||||
|
holder.lock.Lock()
|
||||||
|
r.runner.Schedule(func() {
|
||||||
|
defer holder.lock.Unlock()
|
||||||
|
o := r.handle(v)
|
||||||
|
holder.value <- o
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits all the messages to be processed and taken from inner buffer.
|
||||||
|
func (r *StableRunner[I, O]) Wait() {
|
||||||
|
close(r.done)
|
||||||
|
r.runner.Wait()
|
||||||
|
for atomic.LoadUint64(&r.consumedIndex) < atomic.LoadUint64(&r.writtenIndex) {
|
||||||
|
runtime.Gosched()
|
||||||
|
}
|
||||||
|
}
|
||||||
97
core/threading/stablerunner_test.go
Normal file
97
core/threading/stablerunner_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package threading
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStableRunner(t *testing.T) {
|
||||||
|
size := bufSize * 2
|
||||||
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
runner := NewStableRunner(func(v int) float64 {
|
||||||
|
if v == 0 {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))
|
||||||
|
}
|
||||||
|
return float64(v) + 0.5
|
||||||
|
})
|
||||||
|
|
||||||
|
var waitGroup sync.WaitGroup
|
||||||
|
waitGroup.Add(1)
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
assert.NoError(t, runner.Push(i))
|
||||||
|
}
|
||||||
|
runner.Wait()
|
||||||
|
waitGroup.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
values := make([]float64, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
var err error
|
||||||
|
values[i], err = runner.Get()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, sort.Float64sAreSorted(values))
|
||||||
|
waitGroup.Wait()
|
||||||
|
|
||||||
|
assert.Equal(t, ErrRunnerClosed, runner.Push(1))
|
||||||
|
_, err := runner.Get()
|
||||||
|
assert.Equal(t, ErrRunnerClosed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzStableRunner(f *testing.F) {
|
||||||
|
rand.NewSource(time.Now().UnixNano())
|
||||||
|
f.Add(uint64(bufSize))
|
||||||
|
f.Fuzz(func(t *testing.T, n uint64) {
|
||||||
|
runner := NewStableRunner(func(v int) float64 {
|
||||||
|
if v == 0 {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))
|
||||||
|
}
|
||||||
|
return float64(v) + 0.5
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < int(n); i++ {
|
||||||
|
assert.NoError(t, runner.Push(i))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
values := make([]float64, n)
|
||||||
|
for i := 0; i < int(n); i++ {
|
||||||
|
var err error
|
||||||
|
values[i], err = runner.Get()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.Wait()
|
||||||
|
assert.True(t, sort.Float64sAreSorted(values))
|
||||||
|
|
||||||
|
// make sure returning errors after runner is closed
|
||||||
|
assert.Equal(t, ErrRunnerClosed, runner.Push(1))
|
||||||
|
_, err := runner.Get()
|
||||||
|
assert.Equal(t, ErrRunnerClosed, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStableRunner(b *testing.B) {
|
||||||
|
runner := NewStableRunner(func(v int) float64 {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))
|
||||||
|
return float64(v) + 0.5
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = runner.Push(i)
|
||||||
|
_, _ = runner.Get()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
package threading
|
package threading
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/lang"
|
"github.com/zeromicro/go-zero/core/lang"
|
||||||
"github.com/zeromicro/go-zero/core/rescue"
|
"github.com/zeromicro/go-zero/core/rescue"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrTaskRunnerBusy is the error that indicates the runner is busy.
|
||||||
|
var ErrTaskRunnerBusy = errors.New("task runner is busy")
|
||||||
|
|
||||||
// A TaskRunner is used to control the concurrency of goroutines.
|
// A TaskRunner is used to control the concurrency of goroutines.
|
||||||
type TaskRunner struct {
|
type TaskRunner struct {
|
||||||
limitChan chan lang.PlaceholderType
|
limitChan chan lang.PlaceholderType
|
||||||
|
waitGroup sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTaskRunner returns a TaskRunner.
|
// NewTaskRunner returns a TaskRunner.
|
||||||
@@ -19,13 +26,47 @@ func NewTaskRunner(concurrency int) *TaskRunner {
|
|||||||
|
|
||||||
// Schedule schedules a task to run under concurrency control.
|
// Schedule schedules a task to run under concurrency control.
|
||||||
func (rp *TaskRunner) Schedule(task func()) {
|
func (rp *TaskRunner) Schedule(task func()) {
|
||||||
|
// Why we add waitGroup first, in case of race condition on starting a task and wait returns.
|
||||||
|
// For example, limitChan is full, and the task is scheduled to run, but the waitGroup is not added,
|
||||||
|
// then the wait returns, and the task is then scheduled to run, but caller thinks all tasks are done.
|
||||||
|
// the same reason for ScheduleImmediately.
|
||||||
|
rp.waitGroup.Add(1)
|
||||||
rp.limitChan <- lang.Placeholder
|
rp.limitChan <- lang.Placeholder
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer rescue.Recover(func() {
|
defer rescue.Recover(func() {
|
||||||
<-rp.limitChan
|
<-rp.limitChan
|
||||||
|
rp.waitGroup.Done()
|
||||||
})
|
})
|
||||||
|
|
||||||
task()
|
task()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScheduleImmediately schedules a task to run immediately under concurrency control.
|
||||||
|
// It returns ErrTaskRunnerBusy if the runner is busy.
|
||||||
|
func (rp *TaskRunner) ScheduleImmediately(task func()) error {
|
||||||
|
// Why we add waitGroup first, check the comment in Schedule.
|
||||||
|
rp.waitGroup.Add(1)
|
||||||
|
select {
|
||||||
|
case rp.limitChan <- lang.Placeholder:
|
||||||
|
default:
|
||||||
|
rp.waitGroup.Done()
|
||||||
|
return ErrTaskRunnerBusy
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer rescue.Recover(func() {
|
||||||
|
<-rp.limitChan
|
||||||
|
rp.waitGroup.Done()
|
||||||
|
})
|
||||||
|
task()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits all running tasks to be done.
|
||||||
|
func (rp *TaskRunner) Wait() {
|
||||||
|
rp.waitGroup.Wait()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,32 +2,52 @@ package threading
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRoutinePool(t *testing.T) {
|
func TestTaskRunner_Schedule(t *testing.T) {
|
||||||
times := 100
|
times := 100
|
||||||
pool := NewTaskRunner(runtime.NumCPU())
|
pool := NewTaskRunner(runtime.NumCPU())
|
||||||
|
|
||||||
var counter int32
|
var counter int32
|
||||||
var waitGroup sync.WaitGroup
|
|
||||||
for i := 0; i < times; i++ {
|
for i := 0; i < times; i++ {
|
||||||
waitGroup.Add(1)
|
|
||||||
pool.Schedule(func() {
|
pool.Schedule(func() {
|
||||||
atomic.AddInt32(&counter, 1)
|
atomic.AddInt32(&counter, 1)
|
||||||
waitGroup.Done()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
waitGroup.Wait()
|
pool.Wait()
|
||||||
|
|
||||||
assert.Equal(t, times, int(counter))
|
assert.Equal(t, times, int(counter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskRunner_ScheduleImmediately(t *testing.T) {
|
||||||
|
cpus := runtime.NumCPU()
|
||||||
|
times := cpus * 2
|
||||||
|
pool := NewTaskRunner(cpus)
|
||||||
|
|
||||||
|
var counter int32
|
||||||
|
for i := 0; i < times; i++ {
|
||||||
|
err := pool.ScheduleImmediately(func() {
|
||||||
|
atomic.AddInt32(&counter, 1)
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
})
|
||||||
|
if i < cpus {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
} else {
|
||||||
|
assert.ErrorIs(t, err, ErrTaskRunnerBusy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Wait()
|
||||||
|
|
||||||
|
assert.Equal(t, cpus, int(counter))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRoutinePool(b *testing.B) {
|
func BenchmarkRoutinePool(b *testing.B) {
|
||||||
queue := NewTaskRunner(runtime.NumCPU())
|
queue := NewTaskRunner(runtime.NumCPU())
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
30
go.mod
30
go.mod
@@ -12,14 +12,14 @@ require (
|
|||||||
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.3
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.5.2
|
github.com/jackc/pgx/v5 v5.5.3
|
||||||
github.com/jhump/protoreflect v1.15.6
|
github.com/jhump/protoreflect v1.15.6
|
||||||
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.1.1
|
||||||
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.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
go.etcd.io/etcd/api/v3 v3.5.12
|
go.etcd.io/etcd/api/v3 v3.5.12
|
||||||
go.etcd.io/etcd/client/v3 v3.5.12
|
go.etcd.io/etcd/client/v3 v3.5.12
|
||||||
go.mongodb.org/mongo-driver v1.13.1
|
go.mongodb.org/mongo-driver v1.13.1
|
||||||
@@ -33,18 +33,18 @@ 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.20.0
|
golang.org/x/net v0.21.0
|
||||||
golang.org/x/sys v0.16.0
|
golang.org/x/sys v0.17.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80
|
||||||
google.golang.org/grpc v1.61.0
|
google.golang.org/grpc v1.62.0
|
||||||
google.golang.org/protobuf v1.32.0
|
google.golang.org/protobuf v1.32.0
|
||||||
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.1
|
k8s.io/api v0.29.2
|
||||||
k8s.io/apimachinery v0.29.1
|
k8s.io/apimachinery v0.29.2
|
||||||
k8s.io/client-go v0.29.1
|
k8s.io/client-go v0.29.2
|
||||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,14 +104,14 @@ require (
|
|||||||
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.18.0 // indirect
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
golang.org/x/oauth2 v0.14.0 // indirect
|
golang.org/x/oauth2 v0.16.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/term v0.16.0 // indirect
|
golang.org/x/term v0.17.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.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-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // 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
|
||||||
|
|||||||
63
go.sum
63
go.sum
@@ -56,7 +56,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
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.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
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/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=
|
||||||
@@ -88,8 +88,8 @@ 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.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA=
|
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
|
||||||
github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
github.com/jackc/pgx/v5 v5.5.3/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.15.6 h1:WMYJbw2Wo+KOWwZFvgY0jMoVHM6i4XIvRs2RcBj5VmI=
|
||||||
@@ -163,15 +163,16 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
|||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
|
||||||
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/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=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
@@ -233,8 +234,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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
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=
|
||||||
@@ -247,17 +248,17 @@ 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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||||
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=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -271,12 +272,12 @@ 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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
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=
|
||||||
@@ -300,14 +301,14 @@ 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-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
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-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||||
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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
@@ -327,12 +328,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.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw=
|
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
|
||||||
k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ=
|
k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0=
|
||||||
k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc=
|
k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=
|
||||||
k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
|
k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
|
||||||
k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A=
|
k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg=
|
||||||
k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks=
|
k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA=
|
||||||
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=
|
||||||
|
|||||||
@@ -120,12 +120,12 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
|||||||
# docker for amd64 architecture
|
# docker for amd64 architecture
|
||||||
docker pull kevinwan/goctl
|
docker pull kevinwan/goctl
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl --help
|
||||||
|
|
||||||
# docker for arm64(Mac) architecture
|
# docker for arm64(Mac) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 --help
|
||||||
```
|
```
|
||||||
|
|
||||||
确保 goctl 可执行
|
确保 goctl 可执行
|
||||||
@@ -300,14 +300,15 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>96. Sonderbase Technologies
|
>96. Sonderbase Technologies
|
||||||
>97. 上海荣时信息科技有限公司
|
>97. 上海荣时信息科技有限公司
|
||||||
>98. 上海同犀智能科技有限公司
|
>98. 上海同犀智能科技有限公司
|
||||||
|
>99. 新华三技术有限公司
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
## 10. CNCF 云原生技术全景图
|
## 10. CNCF 云原生技术全景图
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>
|
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-logo.svg" width="200"/>
|
||||||
<img src="https://landscape.cncf.io/images/right-logo.svg" width="200"/>
|
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-landscape-logo.svg" width="150"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape.cncf.io/?selected=go-zero)。
|
go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape.cncf.io/?selected=go-zero)。
|
||||||
|
|||||||
@@ -127,12 +127,12 @@ go get -u github.com/zeromicro/go-zero
|
|||||||
# docker for amd64 architecture
|
# docker for amd64 architecture
|
||||||
docker pull kevinwan/goctl
|
docker pull kevinwan/goctl
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl --help
|
||||||
|
|
||||||
# docker for arm64(Mac) architecture
|
# docker for arm64(Mac) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 --help
|
||||||
```
|
```
|
||||||
|
|
||||||
make sure goctl is executable.
|
make sure goctl is executable.
|
||||||
@@ -248,8 +248,8 @@ Join the chat via https://discord.gg/4JQvC5A4Fe
|
|||||||
## Cloud Native Landscape
|
## Cloud Native Landscape
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
<img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>
|
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-logo.svg" width="200"/>
|
||||||
<img src="https://landscape.cncf.io/images/right-logo.svg" width="200"/>
|
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-landscape-logo.svg" width="150"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
go-zero enlisted in the [CNCF Cloud Native Landscape](https://landscape.cncf.io/?selected=go-zero).
|
go-zero enlisted in the [CNCF Cloud Native Landscape](https://landscape.cncf.io/?selected=go-zero).
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ type (
|
|||||||
MaxBytes int64 `json:",default=1048576"`
|
MaxBytes int64 `json:",default=1048576"`
|
||||||
// milliseconds
|
// milliseconds
|
||||||
Timeout int64 `json:",default=3000"`
|
Timeout int64 `json:",default=3000"`
|
||||||
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
CpuThreshold int64 `json:",default=900,range=[0:1000)"`
|
||||||
Signature SignatureConf `json:",optional"`
|
Signature SignatureConf `json:",optional"`
|
||||||
// There are default values for all the items in Middlewares.
|
// There are default values for all the items in Middlewares.
|
||||||
Middlewares MiddlewaresConf
|
Middlewares MiddlewaresConf
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
Name: "duration_ms",
|
Name: "duration_ms",
|
||||||
Help: "http server requests duration(ms).",
|
Help: "http server requests duration(ms).",
|
||||||
Labels: []string{"path", "method"},
|
Labels: []string{"path", "method"},
|
||||||
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
|
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 750, 1000},
|
||||||
})
|
})
|
||||||
|
|
||||||
metricServerReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
|
metricServerReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const (
|
|||||||
reason = "Request Timeout"
|
reason = "Request Timeout"
|
||||||
headerUpgrade = "Upgrade"
|
headerUpgrade = "Upgrade"
|
||||||
valueWebsocket = "websocket"
|
valueWebsocket = "websocket"
|
||||||
|
headerAccept = "Accept"
|
||||||
|
valueSSE = "text/event-stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TimeoutHandler returns the handler with given timeout.
|
// TimeoutHandler returns the handler with given timeout.
|
||||||
@@ -56,7 +58,9 @@ func (h *timeoutHandler) errorBody() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get(headerUpgrade) == valueWebsocket {
|
if r.Header.Get(headerUpgrade) == valueWebsocket ||
|
||||||
|
// Server-Sent Event ignore timeout.
|
||||||
|
r.Header.Get(headerAccept) == valueSSE {
|
||||||
h.handler.ServeHTTP(w, r)
|
h.handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -110,7 +114,7 @@ func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
}
|
}
|
||||||
io.WriteString(w, h.errorBody())
|
_, _ = io.WriteString(w, h.errorBody())
|
||||||
})
|
})
|
||||||
tw.timedOut = true
|
tw.timedOut = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,23 @@ func TestTimeoutPanic(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeoutSSE(t *testing.T) {
|
||||||
|
timeoutHandler := TimeoutHandler(time.Millisecond)
|
||||||
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
r.Header.Set("Content-Type", "text/event-stream")
|
||||||
|
r.Header.Set("Cache-Control", "no-cache")
|
||||||
|
r.Header.Set("Connection", "keep-alive")
|
||||||
|
r.Header.Set("Transfer-Encoding", "chunked")
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
|
req.Header.Set(headerAccept, valueSSE)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(resp, req)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimeoutWebsocket(t *testing.T) {
|
func TestTimeoutWebsocket(t *testing.T) {
|
||||||
timeoutHandler := TimeoutHandler(time.Millisecond)
|
timeoutHandler := TimeoutHandler(time.Millisecond)
|
||||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
8
tools/goctl/.dockerignore
Normal file
8
tools/goctl/.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
test/
|
||||||
|
.dockerignore
|
||||||
|
.go-version
|
||||||
|
Dockerfile
|
||||||
|
goctl
|
||||||
|
Makefile
|
||||||
|
readme.md
|
||||||
|
readme-cn.md
|
||||||
@@ -8,13 +8,12 @@ ENV GOPROXY https://goproxy.cn,direct
|
|||||||
RUN apk update --no-cache && apk add --no-cache tzdata
|
RUN apk update --no-cache && apk add --no-cache tzdata
|
||||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
RUN addgroup -g 1000 -S app && adduser -u 1000 -S app -G app
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
ADD go.mod .
|
|
||||||
ADD go.sum .
|
|
||||||
RUN go mod download
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN go mod download
|
||||||
RUN go build -ldflags="-s -w" -o /app/goctl ./goctl.go
|
RUN go build -ldflags="-s -w" -o /app/goctl ./goctl.go
|
||||||
|
|
||||||
|
|
||||||
@@ -22,13 +21,20 @@ FROM golang:alpine
|
|||||||
|
|
||||||
RUN apk update --no-cache && apk add --no-cache protoc
|
RUN apk update --no-cache && apk add --no-cache protoc
|
||||||
|
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/passwd /etc/group /etc/
|
||||||
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
|
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
|
||||||
COPY --from=builder /go/bin/protoc-gen-go /usr/bin/protoc-gen-go
|
COPY --from=builder --chown=1000:1000 /go/bin/protoc-gen-go* /app/goctl /usr/local/bin/
|
||||||
COPY --from=builder /go/bin/protoc-gen-go-grpc /usr/bin/protoc-gen-go-grpc
|
|
||||||
ENV TZ Asia/Shanghai
|
ENV TZ Asia/Shanghai
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/goctl /usr/bin/goctl
|
USER app
|
||||||
|
|
||||||
CMD ["goctl"]
|
LABEL org.opencontainers.image.authors="Kevin Wan"
|
||||||
|
LABEL org.opencontainers.image.base.name="docker.io/library/golang:alpine"
|
||||||
|
LABEL org.opencontainers.image.description="A cloud-native Go microservices framework with cli tool for productivity."
|
||||||
|
LABEL org.opencontainers.image.licenses="MIT"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/zeromicro/go-zero"
|
||||||
|
LABEL org.opencontainers.image.title="goctl (cli)"
|
||||||
|
LABEL org.opencontainers.image.version="v1.6.2"
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/goctl"]
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ type parser struct {
|
|||||||
spec *spec.ApiSpec
|
spec *spec.ApiSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse parses the api file.
|
||||||
// Depreacted: use tools/goctl/pkg/parser/api/parser/parser.go:18 instead,
|
// Depreacted: use tools/goctl/pkg/parser/api/parser/parser.go:18 instead,
|
||||||
// it will be removed in the future.
|
// it will be removed in the future.
|
||||||
// Parse parses the api file.
|
|
||||||
func Parse(filename string) (*spec.ApiSpec, error) {
|
func Parse(filename string) (*spec.ApiSpec, error) {
|
||||||
if env.UseExperimental() {
|
if env.UseExperimental() {
|
||||||
return apiParser.Parse(filename, "")
|
return apiParser.Parse(filename, "")
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ require (
|
|||||||
github.com/iancoleman/strcase v0.3.0
|
github.com/iancoleman/strcase v0.3.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
|
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
|
||||||
github.com/zeromicro/antlr v0.0.1
|
github.com/zeromicro/antlr v0.0.1
|
||||||
github.com/zeromicro/ddl-parser v1.0.5
|
github.com/zeromicro/ddl-parser v1.0.5
|
||||||
github.com/zeromicro/go-zero v1.6.2
|
github.com/zeromicro/go-zero v1.6.2
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.61.0
|
google.golang.org/grpc v1.62.0
|
||||||
google.golang.org/protobuf v1.32.0
|
google.golang.org/protobuf v1.32.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,15 +93,15 @@ require (
|
|||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/oauth2 v0.14.0 // indirect
|
golang.org/x/oauth2 v0.16.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/term v0.16.0 // indirect
|
golang.org/x/term v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.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-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
|
|||||||
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=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
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/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=
|
||||||
@@ -155,15 +155,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
|
||||||
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/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=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
|
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
|
||||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
|
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
@@ -236,15 +237,15 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||||
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=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -284,14 +285,14 @@ 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-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
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-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||||
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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
|||||||
@@ -84,6 +84,20 @@ func (t *TokenNode) SetLeadingCommentGroup(cg CommentGroup) {
|
|||||||
t.LeadingCommentGroup = cg
|
t.LeadingCommentGroup = cg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawText returns the node's raw text.
|
||||||
|
func (t *TokenNode) RawText() string {
|
||||||
|
text := t.Token.Text
|
||||||
|
if strings.HasPrefix(text, "`") {
|
||||||
|
text = strings.TrimPrefix(text, "`")
|
||||||
|
text = strings.TrimSuffix(text, "`")
|
||||||
|
} else if strings.HasPrefix(text, `"`) {
|
||||||
|
text = strings.TrimPrefix(text, `"`)
|
||||||
|
text = strings.TrimSuffix(text, `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TokenNode) HasLeadingCommentGroup() bool {
|
func (t *TokenNode) HasLeadingCommentGroup() bool {
|
||||||
return t.LeadingCommentGroup.Valid() || t.leadingFlag
|
return t.LeadingCommentGroup.Valid() || t.leadingFlag
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Analyzer) convert2Spec() error {
|
func (a *Analyzer) convert2Spec() error {
|
||||||
|
a.fillInfo()
|
||||||
|
|
||||||
if err := a.fillTypes(); err != nil {
|
if err := a.fillTypes(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -128,7 +130,7 @@ func (a *Analyzer) convert2Spec() error {
|
|||||||
groups = append(groups, v)
|
groups = append(groups, v)
|
||||||
}
|
}
|
||||||
sort.SliceStable(groups, func(i, j int) bool {
|
sort.SliceStable(groups, func(i, j int) bool {
|
||||||
return groups[i].Annotation.Properties["group"] < groups[j].Annotation.Properties["group"]
|
return groups[i].Annotation.Properties[groupKeyText] < groups[j].Annotation.Properties[groupKeyText]
|
||||||
})
|
})
|
||||||
a.spec.Service.Groups = groups
|
a.spec.Service.Groups = groups
|
||||||
|
|
||||||
@@ -150,8 +152,13 @@ func (a *Analyzer) convertKV(kv []*ast.KVExpr) map[string]string {
|
|||||||
var ret = map[string]string{}
|
var ret = map[string]string{}
|
||||||
for _, v := range kv {
|
for _, v := range kv {
|
||||||
key := strings.TrimSuffix(v.Key.Token.Text, ":")
|
key := strings.TrimSuffix(v.Key.Token.Text, ":")
|
||||||
ret[key] = v.Value.Token.Text
|
if key == summaryKeyText {
|
||||||
|
ret[key] = v.Value.RawText()
|
||||||
|
} else {
|
||||||
|
ret[key] = v.Value.Token.Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +277,27 @@ func (a *Analyzer) fillService() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) fillInfo() {
|
||||||
|
properties := make(map[string]string)
|
||||||
|
if a.api.info != nil {
|
||||||
|
for _, kv := range a.api.info.Values {
|
||||||
|
key := kv.Key.Token.Text
|
||||||
|
properties[strings.TrimSuffix(key, ":")] = kv.Value.RawText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.spec.Info.Properties = properties
|
||||||
|
infoKeyValue := make(map[string]string)
|
||||||
|
for key, value := range properties {
|
||||||
|
titleKey := strings.Title(strings.TrimSuffix(key, ":"))
|
||||||
|
infoKeyValue[titleKey] = value
|
||||||
|
}
|
||||||
|
a.spec.Info.Title = infoKeyValue[infoTitleKey]
|
||||||
|
a.spec.Info.Desc = infoKeyValue[infoDescKey]
|
||||||
|
a.spec.Info.Version = infoKeyValue[infoVersionKey]
|
||||||
|
a.spec.Info.Author = infoKeyValue[infoAuthorKey]
|
||||||
|
a.spec.Info.Email = infoKeyValue[infoEmailKey]
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Analyzer) fillTypes() error {
|
func (a *Analyzer) fillTypes() error {
|
||||||
for _, item := range a.api.TypeStmt {
|
for _, item := range a.api.TypeStmt {
|
||||||
switch v := (item).(type) {
|
switch v := (item).(type) {
|
||||||
|
|||||||
@@ -9,14 +9,40 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Parse(t *testing.T) {
|
func Test_Parse(t *testing.T) {
|
||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
_, err := Parse("./testdata/example.api", nil)
|
apiSpec, err := Parse("./testdata/example.api", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
ast := assert.New(t)
|
||||||
|
ast.Equal(spec.Info{
|
||||||
|
Title: "type title here",
|
||||||
|
Desc: "type desc here",
|
||||||
|
Version: "type version here",
|
||||||
|
Author: "type author here",
|
||||||
|
Email: "type email here",
|
||||||
|
Properties: map[string]string{
|
||||||
|
"title": "type title here",
|
||||||
|
"desc": "type desc here",
|
||||||
|
"version": "type version here",
|
||||||
|
"author": "type author here",
|
||||||
|
"email": "type email here",
|
||||||
|
},
|
||||||
|
}, apiSpec.Info)
|
||||||
|
ast.True(func() bool {
|
||||||
|
for _, group := range apiSpec.Service.Groups {
|
||||||
|
value, ok := group.Annotation.Properties["summary"]
|
||||||
|
if ok {
|
||||||
|
return value == "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
data, err := os.ReadFile("./testdata/invalid.api")
|
data, err := os.ReadFile("./testdata/invalid.api")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -38,12 +64,19 @@ func Test_Parse(t *testing.T) {
|
|||||||
assertx.Error(t, err)
|
assertx.Error(t, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("circleImport", func(t *testing.T) {
|
t.Run("circleImport", func(t *testing.T) {
|
||||||
_, err := Parse("./testdata/base.api", nil)
|
_, err := Parse("./testdata/base.api", nil)
|
||||||
assertx.Error(t, err)
|
assertx.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("link_import", func(t *testing.T) {
|
t.Run("link_import", func(t *testing.T) {
|
||||||
_, err := Parse("./testdata/link_import.api", nil)
|
_, err := Parse("./testdata/link_import.api", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate_types", func(t *testing.T) {
|
||||||
|
_, err := Parse("./testdata/duplicate_type.api", nil)
|
||||||
|
assertx.Error(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
type filterBuilder struct {
|
type filterBuilder struct {
|
||||||
filename string
|
filename string
|
||||||
m map[string]placeholder.Type
|
m map[string]token.Position
|
||||||
checkExprName string
|
checkExprName string
|
||||||
errorManager *errorManager
|
errorManager *errorManager
|
||||||
}
|
}
|
||||||
@@ -17,10 +17,10 @@ type filterBuilder struct {
|
|||||||
func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
|
func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
fileNodeText := fmt.Sprintf("%s/%s", b.filename, node.Token.Text)
|
fileNodeText := fmt.Sprintf("%s/%s", b.filename, node.Token.Text)
|
||||||
if _, ok := b.m[fileNodeText]; ok {
|
if pos, ok := b.m[fileNodeText]; ok && pos != node.Token.Position {
|
||||||
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
||||||
} else {
|
} else {
|
||||||
b.m[fileNodeText] = placeholder.PlaceHolder
|
b.m[fileNodeText] = node.Token.Position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,10 +28,10 @@ func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
|
|||||||
func (b *filterBuilder) checkNodeWithPrefix(prefix string, nodes ...*ast.TokenNode) {
|
func (b *filterBuilder) checkNodeWithPrefix(prefix string, nodes ...*ast.TokenNode) {
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
joinText := fmt.Sprintf("%s/%s", prefix, node.Token.Text)
|
joinText := fmt.Sprintf("%s/%s", prefix, node.Token.Text)
|
||||||
if _, ok := b.m[joinText]; ok {
|
if pos, ok := b.m[joinText]; ok && pos != node.Token.Position {
|
||||||
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
||||||
} else {
|
} else {
|
||||||
b.m[joinText] = placeholder.PlaceHolder
|
b.m[joinText] = node.Token.Position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func newFilter() *filter {
|
|||||||
func (f *filter) addCheckItem(filename, checkExprName string) *filterBuilder {
|
func (f *filter) addCheckItem(filename, checkExprName string) *filterBuilder {
|
||||||
b := &filterBuilder{
|
b := &filterBuilder{
|
||||||
filename: filename,
|
filename: filename,
|
||||||
m: make(map[string]placeholder.Type),
|
m: make(map[string]token.Position),
|
||||||
checkExprName: checkExprName,
|
checkExprName: checkExprName,
|
||||||
errorManager: newErrorManager(),
|
errorManager: newErrorManager(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,17 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
const idAPI = "api"
|
const (
|
||||||
|
idAPI = "api"
|
||||||
|
summaryKeyExprText = "summary:"
|
||||||
|
summaryKeyText = "summary"
|
||||||
|
groupKeyText = "group"
|
||||||
|
infoTitleKey = "Title"
|
||||||
|
infoDescKey = "Desc"
|
||||||
|
infoVersionKey = "Version"
|
||||||
|
infoAuthorKey = "Author"
|
||||||
|
infoEmailKey = "Email"
|
||||||
|
)
|
||||||
|
|
||||||
// Parser is the parser for api file.
|
// Parser is the parser for api file.
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
@@ -1134,7 +1144,7 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
|||||||
|
|
||||||
var valueTok token.Token
|
var valueTok token.Token
|
||||||
var leadingCommentGroup ast.CommentGroup
|
var leadingCommentGroup ast.CommentGroup
|
||||||
if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT) {
|
if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT, token.STRING) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,13 +1154,27 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slashTok := p.curTok
|
slashTok := p.curTok
|
||||||
|
var pathText = slashTok.Text
|
||||||
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
idTok := p.curTok
|
pathText += p.curTok.Text
|
||||||
|
if p.peekTokenIs(token.SUB) { // parse abc-efg format
|
||||||
|
if !p.nextToken() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathText += p.curTok.Text
|
||||||
|
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathText += p.curTok.Text
|
||||||
|
}
|
||||||
|
|
||||||
valueTok = token.Token{
|
valueTok = token.Token{
|
||||||
Text: slashTok.Text + idTok.Text,
|
Text: pathText,
|
||||||
Position: slashTok.Position,
|
Position: slashTok.Position,
|
||||||
}
|
}
|
||||||
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
||||||
@@ -1170,6 +1194,23 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
valueTok = p.curTok
|
||||||
|
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
||||||
|
node := ast.NewTokenNode(valueTok)
|
||||||
|
node.SetLeadingCommentGroup(leadingCommentGroup)
|
||||||
|
expr.Value = node
|
||||||
|
return expr
|
||||||
|
} else if p.peekTokenIs(token.STRING) {
|
||||||
|
if expr.Key.Token.Text != summaryKeyExprText {
|
||||||
|
if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.nextToken() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
valueTok = p.curTok
|
valueTok = p.curTok
|
||||||
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
||||||
node := ast.NewTokenNode(valueTok)
|
node := ast.NewTokenNode(valueTok)
|
||||||
@@ -1221,13 +1262,28 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slashTok := p.curTok
|
slashTok := p.curTok
|
||||||
|
var pathText = valueTok.Text
|
||||||
|
pathText += slashTok.Text
|
||||||
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
idTok := p.curTok
|
pathText += p.curTok.Text
|
||||||
|
if p.peekTokenIs(token.SUB) { // parse abc-efg format
|
||||||
|
if !p.nextToken() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathText += p.curTok.Text
|
||||||
|
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathText += p.curTok.Text
|
||||||
|
}
|
||||||
|
|
||||||
valueTok = token.Token{
|
valueTok = token.Token{
|
||||||
Text: valueTok.Text + slashTok.Text + idTok.Text,
|
Text: pathText,
|
||||||
Position: valueTok.Position,
|
Position: valueTok.Position,
|
||||||
}
|
}
|
||||||
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
||||||
|
|||||||
@@ -299,6 +299,11 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
|
|||||||
"timeout6:": "10ns",
|
"timeout6:": "10ns",
|
||||||
"timeout7:": "1h10m10s10ms10µs10ns",
|
"timeout7:": "1h10m10s10ms10µs10ns",
|
||||||
"maxBytes:": `1024`,
|
"maxBytes:": `1024`,
|
||||||
|
"prefix:": "/v1",
|
||||||
|
"prefix1:": "/v1/v2_test/v2-beta",
|
||||||
|
"prefix2:": "v1/v2_test/v2-beta",
|
||||||
|
"prefix3:": "v1/v2_",
|
||||||
|
"summary:": `"test"`,
|
||||||
}
|
}
|
||||||
|
|
||||||
p := New("foo.api", atServerTestAPI)
|
p := New("foo.api", atServerTestAPI)
|
||||||
@@ -349,6 +354,8 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
|
|||||||
`@server(foo:/v1/v2`,
|
`@server(foo:/v1/v2`,
|
||||||
`@server(foo: m1,`,
|
`@server(foo: m1,`,
|
||||||
`@server(foo: m1,)`,
|
`@server(foo: m1,)`,
|
||||||
|
`@server(foo: v1/v2-)`,
|
||||||
|
`@server(foo:"test")`,
|
||||||
}
|
}
|
||||||
for _, v := range testData {
|
for _, v := range testData {
|
||||||
p := New("foo.api", v)
|
p := New("foo.api", v)
|
||||||
|
|||||||
@@ -13,4 +13,9 @@
|
|||||||
timeout6: 10ns
|
timeout6: 10ns
|
||||||
timeout7: 1h10m10s10ms10µs10ns
|
timeout7: 1h10m10s10ms10µs10ns
|
||||||
maxBytes: 1024
|
maxBytes: 1024
|
||||||
)
|
prefix: /v1
|
||||||
|
prefix1: /v1/v2_test/v2-beta
|
||||||
|
prefix2: v1/v2_test/v2-beta
|
||||||
|
prefix3: v1/v2_
|
||||||
|
summary:"test"
|
||||||
|
)
|
||||||
|
|||||||
8
tools/goctl/pkg/parser/api/parser/testdata/duplicate_type.api
vendored
Normal file
8
tools/goctl/pkg/parser/api/parser/testdata/duplicate_type.api
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type Example{
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type Example{
|
||||||
|
B string
|
||||||
|
}
|
||||||
@@ -188,3 +188,17 @@ service example {
|
|||||||
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
|
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: /g1/g2_test/g2_beta
|
||||||
|
prefix: /v1/v2_test/v2-beta
|
||||||
|
summary: "test"
|
||||||
|
)
|
||||||
|
service example {
|
||||||
|
@handler nestDemo1
|
||||||
|
post /a/b_c/d-e/:f/123/g (NestDemoReq1) returns (NestDemoResp1)
|
||||||
|
|
||||||
|
@handler nestDemo2
|
||||||
|
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
tools/goctl/pkg/parser/api/parser/testdata/example_base.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/example_base.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type Base{}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "example_base.api"
|
||||||
|
|
||||||
info(
|
info(
|
||||||
title: "type title here"
|
title: "type title here"
|
||||||
desc: "type desc here"
|
desc: "type desc here"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "example_base.api"
|
||||||
|
|
||||||
info(
|
info(
|
||||||
title: "type title here"
|
title: "type title here"
|
||||||
desc: "type desc here"
|
desc: "type desc here"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"go/build"
|
"go/build"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -51,12 +50,7 @@ func TestRpcGenerate(t *testing.T) {
|
|||||||
err = g.Generate(ctx)
|
err = g.Generate(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
_, err = execx.Run("go test "+projectName, projectDir)
|
_, err = execx.Run("go test "+projectName, projectDir)
|
||||||
if err != nil {
|
assert.Error(t, err)
|
||||||
assert.True(t, func() bool {
|
|
||||||
return strings.Contains(err.Error(),
|
|
||||||
"not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// case go mod
|
// case go mod
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
FROM golang:1.19
|
FROM golang:1.22-alpine
|
||||||
|
|
||||||
ENV TZ Asia/Shanghai
|
ENV TZ Asia/Shanghai
|
||||||
ENV GOPROXY https://goproxy.cn,direct
|
ENV GOPROXY https://goproxy.cn,direct
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD goctl /usr/bin/goctl
|
COPY goctl /usr/bin/
|
||||||
ADD cmd.sh .
|
COPY cmd.sh .
|
||||||
|
|
||||||
RUN chmod +x /usr/bin/goctl
|
RUN chmod +x /usr/bin/goctl cmd.sh
|
||||||
RUN chmod +x cmd.sh
|
|
||||||
CMD ["/bin/bash", "cmd.sh"]
|
CMD ["/bin/bash", "cmd.sh"]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ fi
|
|||||||
|
|
||||||
# run docker image
|
# run docker image
|
||||||
console_step "docker running"
|
console_step "docker running"
|
||||||
docker run $image
|
docker run --rm $image
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
rm -f $buildFile
|
rm -f $buildFile
|
||||||
console_red "docker run failed"
|
console_red "docker run failed"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type (
|
|||||||
StrictControl bool `json:",optional"`
|
StrictControl bool `json:",optional"`
|
||||||
// setting 0 means no timeout
|
// setting 0 means no timeout
|
||||||
Timeout int64 `json:",default=2000"`
|
Timeout int64 `json:",default=2000"`
|
||||||
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
CpuThreshold int64 `json:",default=900,range=[0:1000)"`
|
||||||
// grpc health check switch
|
// grpc health check switch
|
||||||
Health bool `json:",default=true"`
|
Health bool `json:",default=true"`
|
||||||
Middlewares ServerMiddlewaresConf
|
Middlewares ServerMiddlewaresConf
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -27,3 +28,12 @@ func TestUnaryBreakerInterceptor(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnaryBreakerInterceptor_Unavailable(t *testing.T) {
|
||||||
|
_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "any",
|
||||||
|
}, func(_ context.Context, _ any) (any, error) {
|
||||||
|
return nil, breaker.ErrServiceUnavailable
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user