Skip to content

Commit 75b4fca

Browse files
committed
fix #13: refactor tests and docs
1 parent fd5f6d6 commit 75b4fca

File tree

13 files changed

+494
-366
lines changed

13 files changed

+494
-366
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ test-quick:
144144
@go test -timeout $(TIMEOUT) $(PACKAGES)
145145
.PHONY: test-quick
146146

147+
test-verbose:
148+
@go test -race -timeout $(TIMEOUT) -v $(PACKAGES)
149+
.PHONY: test-verbose
150+
147151
test-with-coverage:
148152
@go test \
149153
-cover \

README.md

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -46,64 +46,18 @@ if err := semaphore.Acquire(breaker.BreakByTimeout(time.Minute), 5); err != nil
4646
}
4747
```
4848

49-
more consistent and reliable.
49+
more consistent and reliable. Additionally, I want to implement a Graceful Shutdown
50+
and Circuit Breaker on the same mechanism.
5051

5152
## 🤼‍♂️ How to
5253

53-
```go
54-
import (
55-
"bytes"
56-
"context"
57-
"fmt"
58-
"io"
59-
"net/http"
60-
"os"
61-
"time"
62-
63-
"github.com/kamilsk/breaker"
64-
)
65-
66-
var NewYear = time.Time{}.AddDate(time.Now().Year(), 0, 0)
54+
### Do HTTP request with retries
6755

68-
func Handle(rw http.ResponseWriter, req *http.Request) {
69-
ctx, cancel := context.WithCancel(req.Context())
70-
defer cancel()
71-
72-
deadline, _ := time.ParseDuration(req.Header.Get("X-Timeout"))
73-
interrupter := breaker.Multiplex(
74-
breaker.BreakByContext(context.WithTimeout(ctx, deadline)),
75-
breaker.BreakByDeadline(NewYear),
76-
breaker.BreakBySignal(os.Interrupt),
77-
)
78-
defer interrupter.Close()
79-
80-
buf, work := bytes.NewBuffer(nil), Work(ctx, struct{}{})
81-
for {
82-
select {
83-
case b, ok := <-work:
84-
if !ok {
85-
rw.WriteHeader(http.StatusOK)
86-
io.Copy(rw, buf)
87-
return
88-
}
89-
buf.WriteByte(b)
90-
case <-interrupter.Done():
91-
rw.WriteHeader(http.StatusPartialContent)
92-
rw.Header().Set("Content-Range", fmt.Sprintf("bytes=0-%d", buf.Len()))
93-
io.Copy(rw, buf)
94-
return
95-
}
96-
}
97-
}
98-
99-
func Work(ctx context.Context, _ struct{}) <-chan byte {
100-
outcome := make(chan byte, 1)
56+
...
10157

102-
go func() { ... }()
58+
### Graceful Shutdown HTTP server
10359

104-
return outcome
105-
}
106-
```
60+
...
10761

10862
## 🧩 Integration
10963

@@ -115,6 +69,30 @@ You can use [go modules](https://github.com/golang/go/wiki/Modules) to manage it
11569
$ go get github.com/kamilsk/breaker@latest
11670
```
11771

72+
## 🤲 Outcomes
73+
74+
### Console tool to execute commands for a limited time
75+
76+
The example shows how to execute console commands for ten minutes.
77+
78+
```bash
79+
$ date
80+
# Thu Jan 7 21:02:21
81+
$ breakit after 10m -- database run --port=5432
82+
$ breakit after 10m -- server run --port=8080
83+
$ breakit ps
84+
# +--------------------------+---------------------+
85+
# | Process | Done |
86+
# +--------------------------+---------------------+
87+
# | database run --port=5432 | Thu Jan 7 21:12:24 |
88+
# | server run --port=8080 | Thu Jan 7 21:12:31 |
89+
# +--------------------------+---------------------+
90+
# | Total | 2 |
91+
# +--------------------------+---------------------+
92+
```
93+
94+
See more details [here][cli].
95+
11896
---
11997

12098
made with ❤️ for everyone
@@ -137,5 +115,6 @@ made with ❤️ for everyone
137115
[awesome.page]: https://github.com/avelino/awesome-go#goroutines
138116
[awesome.icon]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg
139117

118+
[cli]: https://github.com/octolab/breakit
140119
[retry]: https://github.com/kamilsk/retry
141120
[semaphore]: https://github.com/kamilsk/semaphore

breaker.go

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,53 +10,104 @@ import (
1010
"time"
1111
)
1212

13-
// New returns a new Breaker which can interrupted only by the Close call.
13+
// New returns a new breaker, which can be interrupted only by a Close call.
14+
//
15+
// interrupter := breaker.New()
16+
// go background.Job().Do(interrupter)
17+
//
18+
// <-time.After(time.Minute)
19+
// interrupter.Close()
20+
//
1421
func New() Interface {
1522
return newBreaker().trigger()
1623
}
1724

18-
// BreakByChannel returns a new Breaker based on the channel.
19-
func BreakByChannel(ch <-chan struct{}) Interface {
20-
return (&channelBreaker{newBreaker(), ch}).trigger()
25+
// BreakByChannel returns a new breaker based on the channel.
26+
//
27+
// signal := make(chan struct{})
28+
// go func() {
29+
// <-time.After(time.Minute)
30+
// close(signal)
31+
// }()
32+
//
33+
// interrupter := breaker.BreakByChannel(signal)
34+
// defer interrupter.Close()
35+
//
36+
// background.Job().Do(interrupter)
37+
//
38+
func BreakByChannel(signal <-chan struct{}) Interface {
39+
return (&channelBreaker{newBreaker(), signal}).trigger()
2140
}
2241

23-
// BreakByContext returns a new Breaker based on the Context.
42+
// BreakByContext returns a new breaker based on the Context.
2443
//
25-
// interrupter := breaker.BreakByContext(context.WithTimeout(req.Context(), time.Minute)),
44+
// interrupter := breaker.BreakByContext(context.WithTimeout(req.Context(), time.Minute))
2645
// defer interrupter.Close()
2746
//
2847
// background.Job().Do(interrupter)
2948
//
3049
func BreakByContext(ctx context.Context, cancel context.CancelFunc) Interface {
31-
return &contextBreaker{ctx, cancel}
50+
return (&contextBreaker{ctx, cancel}).trigger()
3251
}
3352

3453
// BreakByDeadline closes the Done channel when the deadline occurs.
54+
//
55+
// interrupter := breaker.BreakByDeadline(time.Now().Add(time.Minute))
56+
// defer interrupter.Close()
57+
//
58+
// background.Job().Do(interrupter)
59+
//
3560
func BreakByDeadline(deadline time.Time) Interface {
3661
timeout := time.Until(deadline)
3762
if timeout < 0 {
3863
return closedBreaker()
3964
}
40-
return newTimedBreaker(timeout).trigger()
65+
return newTimeoutBreaker(timeout).trigger()
4166
}
4267

43-
// BreakBySignal closes the Done channel when signals will be received.
68+
// BreakBySignal closes the Done channel when the breaker will receive OS signals.
69+
//
70+
// interrupter := breaker.BreakBySignal(os.Interrupt)
71+
// defer interrupter.Close()
72+
//
73+
// background.Job().Do(interrupter)
74+
//
4475
func BreakBySignal(sig ...os.Signal) Interface {
4576
if len(sig) == 0 {
4677
return closedBreaker()
4778
}
48-
return newSignaledBreaker(sig).trigger()
79+
return newSignalBreaker(sig).trigger()
4980
}
5081

5182
// BreakByTimeout closes the Done channel when the timeout happens.
83+
//
84+
// interrupter := breaker.BreakByTimeout(time.Minute)
85+
// defer interrupter.Close()
86+
//
87+
// background.Job().Do(interrupter)
88+
//
5289
func BreakByTimeout(timeout time.Duration) Interface {
5390
if timeout < 0 {
5491
return closedBreaker()
5592
}
56-
return newTimedBreaker(timeout).trigger()
93+
return newTimeoutBreaker(timeout).trigger()
5794
}
5895

59-
// ToContext converts the Breaker into the Context.
96+
// ToContext converts the breaker into the Context.
97+
//
98+
// interrupter := breaker.Multiplex(
99+
// breaker.BreakBySignal(os.Interrupt),
100+
// breaker.BreakByTimeout(time.Minute),
101+
// )
102+
// defer interrupter.Close()
103+
//
104+
// request, err := http.NewRequestWithContext(breaker.ToContext(interrupter), ...)
105+
// if err != nil { handle(err) }
106+
//
107+
// response, err := http.DefaultClient.Do(request)
108+
// if err != nil { handle(err) }
109+
// handle(response)
110+
//
60111
func ToContext(br Interface) context.Context {
61112
ctx, cancel := context.WithCancel(context.Background())
62113
go func() {
@@ -104,7 +155,7 @@ func (br *breaker) Err() error {
104155
return nil
105156
}
106157

107-
// IsReleased returns true if resources associated with the Breaker were released.
158+
// IsReleased returns true if resources associated with the breaker were released.
108159
func (br *breaker) IsReleased() bool {
109160
return atomic.LoadInt32(&br.released) == 1
110161
}
@@ -125,7 +176,7 @@ func (br *channelBreaker) Close() {
125176
})
126177
}
127178

128-
// trigger starts listening internal signal to close the Done channel.
179+
// trigger starts listening to the internal signal to close the Done channel.
129180
func (br *channelBreaker) trigger() Interface {
130181
go func() {
131182
select {
@@ -150,7 +201,7 @@ func (br *contextBreaker) Close() {
150201
br.cancel()
151202
}
152203

153-
// IsReleased returns true if resources associated with the Breaker were released.
204+
// IsReleased returns true if resources associated with the breaker were released.
154205
func (br *contextBreaker) IsReleased() bool {
155206
select {
156207
case <-br.Done():
@@ -164,7 +215,7 @@ func (br *contextBreaker) trigger() Interface {
164215
return br
165216
}
166217

167-
func newSignaledBreaker(signals []os.Signal) *signalBreaker {
218+
func newSignalBreaker(signals []os.Signal) *signalBreaker {
168219
return &signalBreaker{newBreaker(), make(chan os.Signal, len(signals)), signals}
169220
}
170221

@@ -182,7 +233,7 @@ func (br *signalBreaker) Close() {
182233
})
183234
}
184235

185-
// trigger starts listening required signals to close the Done channel.
236+
// trigger starts listening to the required signals to close the Done channel.
186237
func (br *signalBreaker) trigger() Interface {
187238
go func() {
188239
signal.Notify(br.relay, br.signals...)
@@ -198,7 +249,7 @@ func (br *signalBreaker) trigger() Interface {
198249
return br
199250
}
200251

201-
func newTimedBreaker(timeout time.Duration) *timeoutBreaker {
252+
func newTimeoutBreaker(timeout time.Duration) *timeoutBreaker {
202253
return &timeoutBreaker{newBreaker(), time.NewTimer(timeout)}
203254
}
204255

@@ -215,7 +266,7 @@ func (br *timeoutBreaker) Close() {
215266
})
216267
}
217268

218-
// trigger starts listening internal timer to close the Done channel.
269+
// trigger starts listening to the internal timer to close the Done channel.
219270
func (br *timeoutBreaker) trigger() Interface {
220271
go func() {
221272
select {

0 commit comments

Comments
 (0)