Skip to content

Commit 6b1a11f

Browse files
committed
normalize throttler errors
1 parent 251b293 commit 6b1a11f

11 files changed

+560
-194
lines changed

README.MD

+22-22
Original file line numberDiff line numberDiff line change
@@ -134,34 +134,34 @@ thr := NewThrottlerAll( // throttles only if all children throttle
134134

135135
| Throttler | Definition | Description |
136136
|---|---|---|
137-
| echo | `func NewThrottlerEcho(err error) Throttler` | Always throttles with the specified error back. |
137+
| echo | `func NewThrottlerEcho(err error) Throttler` | Always throttles with the specified error back.<br> - could return any specified error; |
138138
| wait | `func NewThrottlerWait(duration time.Duration) Throttler` | Always waits for the specified duration. |
139139
| square | `func NewThrottlerSquare(duration time.Duration, limit time.Duration, reset bool) Throttler` | Always waits for square growing *[1, 4, 9, 16, ...]* multiplier on the specified initial duration, up until the specified duration limit is reached.<br> If reset is set then after throttler riches the specified duration limit next multiplier value will be reseted. |
140140
| jitter | `func NewThrottlerJitter(initial time.Duration, limit time.Duration, reset bool, jitter float64) Throttler` | Waits accordingly to undelying square throttler but also adds the provided jitter delta distribution on top.<br> Jitter value is normalized to [0.0, 1.0] range and defines which part of square delay could be randomized in percents.<br> Implementation uses `math/rand` as PRNG function and expects rand seeding by a client. |
141-
| context | `func NewThrottlerContext() Throttler` | Always throttless on *done* context. |
142-
| panic | `func NewThrottlerPanic() Throttler` | Always panics. |
143-
| each | `func NewThrottlerEach(threshold uint64) Throttler` | Throttles each periodic *i-th* call defined by the specified threshold. |
144-
| before | `func NewThrottlerBefore(threshold uint64) Throttler` | Throttles each call below the *i-th* call defined by the specified threshold. |
145-
| after | `func NewThrottlerAfter(threshold uint64) Throttler` | Throttles each call after the *i-th* call defined by the specified threshold. |
146-
| chance | `func NewThrottlerChance(threshold float64) Throttler` | Throttles each call with the chance *p* defined by the specified threshold.<br> Chance value is normalized to *[0.0, 1.0]* range.<br> Implementation uses `math/rand` as PRNG function and expects rand seeding by a client. |
147-
| running | `func NewThrottlerRunning(threshold uint64) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold. |
141+
| context | `func NewThrottlerContext() Throttler` | Always throttless on *done* context.<br> - could return `ErrorInternal`; |
142+
| panic | `func NewThrottlerPanic() Throttler` | Always panics with `ErrorInternal`. |
143+
| each | `func NewThrottlerEach(threshold uint64) Throttler` | Throttles each periodic *i-th* call defined by the specified threshold.<br> - could return `ErrorThreshold`; |
144+
| before | `func NewThrottlerBefore(threshold uint64) Throttler` | Throttles each call below the *i-th* call defined by the specified threshold.<br> - could return `ErrorThreshold`; |
145+
| after | `func NewThrottlerAfter(threshold uint64) Throttler` | Throttles each call after the *i-th* call defined by the specified threshold.<br> - could return `ErrorThreshold`; |
146+
| chance | `func NewThrottlerChance(threshold float64) Throttler` | Throttles each call with the chance *p* defined by the specified threshold.<br> Chance value is normalized to *[0.0, 1.0]* range.<br> Implementation uses `math/rand` as PRNG function and expects rand seeding by a client.<br> - could return `ErrorThreshold`; |
147+
| running | `func NewThrottlerRunning(threshold uint64) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold.<br> - could return `ErrorThreshold`; |
148148
| buffered | `func NewThrottlerBuffered(threshold uint64) Throttler` | Waits on call which exeeds the running quota *acquired - release* *q* defined by the specified threshold until the running quota is available again. |
149149
| priority | `func NewThrottlerPriority(threshold uint64, levels uint8) Throttler` | Waits on call which exeeds the running quota *acquired - release* *q* defined by the specified threshold until the running quota is available again.<br> Running quota is not equally distributed between *n* levels of priority defined by the specified levels.<br> Use `func WithPriority(ctx context.Context, priority uint8) context.Context` to override context call priority, *1* by default. |
150-
| timed | `func NewThrottlerTimed(threshold uint64, interval time.Duration, quantum time.Duration) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold in the specified interval.<br> Periodically each specified interval the running quota number is reseted.<br> If quantum is set then quantum will be used instead of interval to provide the running quota delta updates. |
151-
| latency | `func NewThrottlerLatency(threshold time.Duration, retention time.Duration) Throttler` | Throttles each call after the call latency *l* defined by the specified threshold was exeeded once.<br> If retention is set then throttler state will be reseted after retention duration.<br> Use `func WithTimestamp(ctx context.Context, ts time.Time) context.Context` to specify running duration between throttler *acquire* and *release*. |
152-
| percentile | `func NewThrottlerPercentile(threshold time.Duration, capacity uint8, percentile float64, retention time.Duration) Throttler` | Throttles each call after the call latency *l* defined by the specified threshold was exeeded once considering the specified percentile.<br> Percentile values are kept in bounded buffer with capacity *c* defined by the specified capacity. <br> If retention is set then throttler state will be reseted after retention duration.<br> Use `func WithTimestamp(ctx context.Context, ts time.Time) context.Context` to specify running duration between throttler *acquire* and *release*. |
153-
| monitor | `func NewThrottlerMonitor(mnt Monitor, threshold Stats) Throttler` | Throttles call if any of the stats returned by provided monitor exceeds any of the stats defined by the specified threshold or if any internal error occurred.<br> Builtin `Monitor` implementations come with stats caching by default.<br> Use builtin `NewMonitorSystem` to create go system monitor instance. |
154-
| metric | `func NewThrottlerMetric(mtc Metric) Throttler` | Throttles call if boolean metric defined by the specified boolean metric is reached or if any internal error occurred.<br> Builtin `Metric` implementations come with boolean metric caching by default.<br> Use builtin `NewMetricPrometheus` to create Prometheus metric instance. |
155-
| enqueuer | `func NewThrottlerEnqueue(enq Enqueuer) Throttler` | Always enqueues message to the specified queue throttles only if any internal error occurred.<br> Use `func WithMessage(ctx context.Context, message interface{}) context.Context` to specify context message for enqueued message and `func WithMarshaler(ctx context.Context, mrsh Marshaler) context.Context` to specify context message marshaler.<br> Builtin `Enqueuer` implementations come with connection reuse and retries by default.<br> Use builtin `func NewEnqueuerRabbit(url string, queue string, retries uint64) Enqueuer` to create RabbitMQ enqueuer instance or `func NewEnqueuerKafka(net string, url string, topic string, retries uint64) Enqueuer` to create Kafka enqueuer instance. |
156-
| adaptive | `func NewThrottlerAdaptive(threshold uint64, interval time.Duration, quantum time.Duration, step uint64, thr Throttler) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold in the specified interval.<br> Periodically each specified interval the running quota number is reseted.<br> If quantum is set then quantum will be used instead of interval to provide the running quota delta updates.<br> Provided adapted throttler adjusts the running quota of adapter throttler by changing the value by *d* defined by the specified step, it subtracts *d^2* from the running quota if adapted throttler throttles or adds *d* to the running quota if it doesn't. |
157-
| pattern | `func NewThrottlerPattern(patterns ...Pattern) Throttler` | Throttles if matching throttler from provided patterns throttles.<br> Use `func WithKey(ctx context.Context, key string) context.Context` to specify key for regexp pattern throttler matching.<br> `Pattern` defines a pair of regexp and related throttler. |
158-
| ring | `func NewThrottlerRing(thrs ...Throttler) Throttler` | Throttles if the *i-th* call throttler from provided list throttle. |
159-
| all | `func NewThrottlerAll(thrs ...Throttler) Throttler` | Throttles call if all provided throttlers throttle. |
160-
| any | `func NewThrottlerAny(thrs ...Throttler) Throttler` | Throttles call if any of provided throttlers throttle. |
161-
| not | `func NewThrottlerNot(thr Throttler) Throttler` | Throttles call if provided throttler doesn't throttle. |
150+
| timed | `func NewThrottlerTimed(threshold uint64, interval time.Duration, quantum time.Duration) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold in the specified interval.<br> Periodically each specified interval the running quota number is reseted.<br> If quantum is set then quantum will be used instead of interval to provide the running quota delta updates.<br> - could return `ErrorThreshold`; |
151+
| latency | `func NewThrottlerLatency(threshold time.Duration, retention time.Duration) Throttler` | Throttles each call after the call latency *l* defined by the specified threshold was exeeded once.<br> If retention is set then throttler state will be reseted after retention duration.<br> Use `func WithTimestamp(ctx context.Context, ts time.Time) context.Context` to specify running duration between throttler *acquire* and *release*.<br> - could return `ErrorThreshold`; |
152+
| percentile | `func NewThrottlerPercentile(threshold time.Duration, capacity uint8, percentile float64, retention time.Duration) Throttler` | Throttles each call after the call latency *l* defined by the specified threshold was exeeded once considering the specified percentile.<br> Percentile values are kept in bounded buffer with capacity *c* defined by the specified capacity. <br> If retention is set then throttler state will be reseted after retention duration.<br> Use `func WithTimestamp(ctx context.Context, ts time.Time) context.Context` to specify running duration between throttler *acquire* and *release*.<br> - could return `ErrorThreshold`; |
153+
| monitor | `func NewThrottlerMonitor(mnt Monitor, threshold Stats) Throttler` | Throttles call if any of the stats returned by provided monitor exceeds any of the stats defined by the specified threshold or if any internal error occurred.<br> Builtin `Monitor` implementations come with stats caching by default.<br> Use builtin `NewMonitorSystem` to create go system monitor instance.<br> - could return `ErrorInternal`;<br> - could return `ErrorThreshold`; |
154+
| metric | `func NewThrottlerMetric(mtc Metric) Throttler` | Throttles call if boolean metric defined by the specified boolean metric is reached or if any internal error occurred.<br> Builtin `Metric` implementations come with boolean metric caching by default.<br> Use builtin `NewMetricPrometheus` to create Prometheus metric instance.<br> - could return `ErrorInternal`;<br> - could return `ErrorThreshold`; |
155+
| enqueuer | `func NewThrottlerEnqueue(enq Enqueuer) Throttler` | Always enqueues message to the specified queue throttles only if any internal error occurred.<br> Use `func WithMessage(ctx context.Context, message interface{}) context.Context` to specify context message for enqueued message and `func WithMarshaler(ctx context.Context, mrsh Marshaler) context.Context` to specify context message marshaler.<br> Builtin `Enqueuer` implementations come with connection reuse and retries by default.<br> Use builtin `func NewEnqueuerRabbit(url string, queue string, retries uint64) Enqueuer` to create RabbitMQ enqueuer instance or `func NewEnqueuerKafka(net string, url string, topic string, retries uint64) Enqueuer` to create Kafka enqueuer instance.<br> - could return `ErrorInternal`; |
156+
| adaptive | `func NewThrottlerAdaptive(threshold uint64, interval time.Duration, quantum time.Duration, step uint64, thr Throttler) Throttler` | Throttles each call which exeeds the running quota *acquired - release* *q* defined by the specified threshold in the specified interval.<br> Periodically each specified interval the running quota number is reseted.<br> If quantum is set then quantum will be used instead of interval to provide the running quota delta updates.<br> Provided adapted throttler adjusts the running quota of adapter throttler by changing the value by *d* defined by the specified step, it subtracts *d^2* from the running quota if adapted throttler throttles or adds *d* to the running quota if it doesn't.<br> - could return `ErrorThreshold`; |
157+
| pattern | `func NewThrottlerPattern(patterns ...Pattern) Throttler` | Throttles if matching throttler from provided patterns throttles.<br> Use `func WithKey(ctx context.Context, key string) context.Context` to specify key for regexp pattern throttler matching.<br> `Pattern` defines a pair of regexp and related throttler.<br> - could return `ErrorInternal`;<br> - could return any underlying throttler error; |
158+
| ring | `func NewThrottlerRing(thrs ...Throttler) Throttler` | Throttles if the *i-th* call throttler from provided list throttle.<br> - could return `ErrorInternal`;<br> - could return any underlying throttler error; |
159+
| all | `func NewThrottlerAll(thrs ...Throttler) Throttler` | Throttles call if all provided throttlers throttle.<br> - could return `ErrorInternal`; |
160+
| any | `func NewThrottlerAny(thrs ...Throttler) Throttler` | Throttles call if any of provided throttlers throttle.<br> - could return `ErrorInternal`; |
161+
| not | `func NewThrottlerNot(thr Throttler) Throttler` | Throttles call if provided throttler doesn't throttle.<br> - could return `ErrorInternal`; |
162162
| suppress | `func NewThrottlerSuppress(thr Throttler) Throttler` | Suppresses provided throttler to never throttle. |
163-
| retry | `func NewThrottlerRetry(thr Throttler, retries uint64) Throttler` | Retries provided throttler error up until the provided retries threshold.<br> Internally retry uses square throttler with `DefaultRetriedDuration` initial duration. |
164-
| cache | `func NewThrottlerCache(thr Throttler, cache time.Duration) Throttler` | Caches provided throttler calls for the provided cache duration, throttler release resulting resets cache.<br> Only non throttling calls are cached for the provided cache duration. |
163+
| retry | `func NewThrottlerRetry(thr Throttler, retries uint64) Throttler` | Retries provided throttler error up until the provided retries threshold.<br> Internally retry uses square throttler with `DefaultRetriedDuration` initial duration.<br> - could return any underlying throttler error; |
164+
| cache | `func NewThrottlerCache(thr Throttler, cache time.Duration) Throttler` | Caches provided throttler calls for the provided cache duration, throttler release resulting resets cache.<br> Only non throttling calls are cached for the provided cache duration.<br> - could return any underlying throttler error; |
165165

166166
## Integrations
167167

context.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (ctx ctxthr) Done() <-chan struct{} {
137137
err := ctx.Err()
138138
if err != nil {
139139
close(ch)
140-
log("context is canceled due %v", err)
140+
log("context is canceled due: %v", err)
141141
}
142142
return err
143143
}))

context_test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
func TestContext(t *testing.T) {
1414
cctx, cancel := context.WithCancel(context.Background())
1515
cancel()
16+
testerr := errors.New("test")
1617
ctx := WithParams(
1718
context.Background(),
1819
time.Now(),
@@ -26,12 +27,15 @@ func TestContext(t *testing.T) {
2627
err error
2728
}{
2829
"Context with throttler should be done on throttling": {
29-
ctx: WithThrottler(context.Background(), tmock{aerr: errors.New("test")}, ms1_0),
30-
err: fmt.Errorf("throttler error has happened %w", errors.New("test")),
30+
ctx: WithThrottler(context.Background(), tmock{aerr: testerr}, ms1_0),
31+
err: fmt.Errorf("throttler error has happened %w", testerr),
3132
},
3233
"Context with throttler should be done on throttling after": {
3334
ctx: WithThrottler(context.Background(), NewThrottlerAfter(1), ms1_0),
34-
err: fmt.Errorf("throttler error has happened %w", errors.New("throttler has exceed threshold")),
35+
err: fmt.Errorf(
36+
"throttler error has happened %w",
37+
ErrorThreshold{Throttler: "after", Threshold: strpair{current: 3, threshold: 1}},
38+
),
3539
},
3640
"Context with throttler should be done with canceled context": {
3741
ctx: WithThrottler(cctx, tmock{}, ms1_0),

errors.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package gohalt
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
type strbool bool
9+
10+
func (b strbool) String() string {
11+
return fmt.Sprintf("%t", bool(b))
12+
}
13+
14+
type strpair struct {
15+
current uint64
16+
threshold uint64
17+
}
18+
19+
func (p strpair) String() string {
20+
return fmt.Sprintf("%d out of %d", p.current, p.threshold)
21+
}
22+
23+
type strpercent float64
24+
25+
func (p strpercent) String() string {
26+
return fmt.Sprintf("%.4f%%", float64(p)*100)
27+
}
28+
29+
type strdurations struct {
30+
current time.Duration
31+
threshold time.Duration
32+
}
33+
34+
func (d strdurations) String() string {
35+
return fmt.Sprintf("%s out of %s", d.current, d.threshold)
36+
}
37+
38+
type strstats struct {
39+
current Stats
40+
threshold Stats
41+
}
42+
43+
func (s strstats) String() string {
44+
return fmt.Sprintf(
45+
`
46+
%d out of %d bytes
47+
%d out of %d bytes
48+
%d out of %d ns
49+
%.4f out of %.4f %%
50+
`,
51+
s.current.MEMAlloc,
52+
s.threshold.MEMAlloc,
53+
s.current.MEMSystem,
54+
s.threshold.MEMSystem,
55+
s.current.CPUPause,
56+
s.threshold.CPUPause,
57+
s.current.CPUUsage*100,
58+
s.threshold.CPUUsage*100,
59+
)
60+
}
61+
62+
// ErrorThreshold defines error type
63+
// that occurs if throttler reaches specified threshold.
64+
type ErrorThreshold struct {
65+
Throttler string
66+
Threshold fmt.Stringer
67+
}
68+
69+
func (err ErrorThreshold) Error() string {
70+
return fmt.Sprintf(
71+
"throttler %q has reached its threshold: %s",
72+
err.Throttler,
73+
err.Threshold,
74+
)
75+
}
76+
77+
// ErrorInternal defines error type
78+
// that occurs if throttler internal error happens.
79+
type ErrorInternal struct {
80+
Throttler string
81+
Message string
82+
}
83+
84+
func (err ErrorInternal) Error() string {
85+
return fmt.Sprintf(
86+
"throttler %q internal error happened: %s",
87+
err.Throttler,
88+
err.Message,
89+
)
90+
}

executors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func retried(retries uint64, run Runnable) Runnable {
9696
if err == nil {
9797
return
9898
}
99-
log("retry error happened %v", err)
99+
log("retry error happened: %v", err)
100100
}
101101
return
102102
}

metrics.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (mtc *mtcprometheus) pull(ctx context.Context, api prometheus.API, query st
7777
return err
7878
}
7979
for _, warn := range warns {
80-
log("prometheus warning happened %s", warn)
80+
log("prometheus warning happened: %s", warn)
8181
}
8282
vec, ok := val.(model.Vector)
8383
if !ok || vec.Len() != 1 {

monitors.go

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ type Stats struct {
2121
CPUUsage float64
2222
}
2323

24+
// Compare checks if provided stats is below current stats.
25+
func (s Stats) Compare(stats Stats) bool {
26+
return (s.MEMAlloc > 0 && stats.MEMAlloc >= s.MEMAlloc) ||
27+
(s.MEMSystem > 0 && stats.MEMSystem >= s.MEMSystem) ||
28+
(s.CPUPause > 0 && stats.CPUPause >= s.CPUPause) ||
29+
(s.CPUUsage > 0 && stats.CPUUsage >= s.CPUUsage)
30+
}
31+
2432
// Monitor defines system monitor interface that returns the system stats.
2533
type Monitor interface {
2634
// Stats returns system stats or internal error if any happened.

runners.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func NewRunnerSync(ctx context.Context, thr Throttler) Runner {
3737
r.err = err
3838
cancel()
3939
}
40-
log("sync runner error happened %v", err)
40+
log("sync runner error happened: %v", err)
4141
}
4242
}
4343
return &r
@@ -97,7 +97,7 @@ func NewRunnerAsync(ctx context.Context, thr Throttler) Runner {
9797
r.err = err
9898
cancel()
9999
})
100-
log("async runner error happened %v", err)
100+
log("async runner error happened: %v", err)
101101
}
102102
}
103103
return &r
@@ -109,7 +109,7 @@ func (r *rasync) Run(run Runnable) {
109109
defer r.wg.Done()
110110
select {
111111
case <-r.ctx.Done():
112-
r.report(fmt.Errorf("context error has happened %w", r.ctx.Err()))
112+
r.report(fmt.Errorf("context error happened %w", r.ctx.Err()))
113113
return
114114
default:
115115
}

0 commit comments

Comments
 (0)