andrey1s
4 years ago
commit
b5f9c60818
23 changed files with 1214 additions and 0 deletions
Binary file not shown.
@ -0,0 +1,25 @@ |
|||
kind: pipeline |
|||
name: default |
|||
|
|||
steps: |
|||
- name: test |
|||
image: golang |
|||
commands: |
|||
- go test -parallel 10 --race ./... |
|||
|
|||
- name: golangci-lint |
|||
image: golangci/golangci-lint:v1.39 |
|||
commands: |
|||
- golangci-lint run |
|||
|
|||
- name: test routine |
|||
image: golang |
|||
commands: |
|||
- cd routine |
|||
- go test -parallel 10 --race ./... |
|||
|
|||
- name: golangci-lint routine |
|||
image: golangci/golangci-lint:v1.39 |
|||
commands: |
|||
- cd routine |
|||
- golangci-lint run |
@ -0,0 +1,17 @@ |
|||
# ---> Go |
|||
# Binaries for programs and plugins |
|||
*.exe |
|||
*.exe~ |
|||
*.dll |
|||
*.so |
|||
*.dylib |
|||
|
|||
# Test binary, built with `go test -c` |
|||
*.test |
|||
|
|||
# Output of the go coverage tool, specifically when used with LiteIDE |
|||
*.out |
|||
|
|||
# Dependency directories (remove the comment below to include it) |
|||
# vendor/ |
|||
|
@ -0,0 +1,46 @@ |
|||
run: |
|||
timeout: 5m |
|||
|
|||
linters-settings: |
|||
dupl: |
|||
threshold: 100 |
|||
funlen: |
|||
lines: 100 |
|||
statements: 50 |
|||
goconst: |
|||
min-len: 2 |
|||
min-occurrences: 2 |
|||
gocyclo: |
|||
min-complexity: 15 |
|||
golint: |
|||
min-confidence: 0 |
|||
govet: |
|||
check-shadowing: true |
|||
lll: |
|||
line-length: 140 |
|||
maligned: |
|||
suggest-new: true |
|||
misspell: |
|||
locale: US |
|||
|
|||
linters: |
|||
enable-all: true |
|||
disable: |
|||
- exhaustivestruct |
|||
- maligned |
|||
- interfacer |
|||
- scopelint |
|||
|
|||
issues: |
|||
# Excluding configuration per-path, per-linter, per-text and per-source |
|||
exclude-rules: |
|||
- path: _test\.go |
|||
linters: |
|||
- gomnd |
|||
- exhaustivestruct |
|||
- wrapcheck |
|||
- path: test/* |
|||
linters: |
|||
- gomnd |
|||
- exhaustivestruct |
|||
- wrapcheck |
@ -0,0 +1,19 @@ |
|||
MIT License Copyright (c) 2020 4devs |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice (including the next |
|||
paragraph) shall be included in all copies or substantial portions of the |
|||
Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS |
|||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF |
|||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,5 @@ |
|||
# closer |
|||
|
|||
[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/closer/status.svg)](https://drone.gitoa.ru/go-4devs/closer) |
|||
[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/closer)](https://goreportcard.com/report/gitoa.ru/go-4devs/closer) |
|||
[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/closer?status.svg)](http://godoc.org/gitoa.ru/go-4devs/closer) |
@ -0,0 +1,109 @@ |
|||
package async |
|||
|
|||
import ( |
|||
"context" |
|||
"os" |
|||
"os/signal" |
|||
"sync" |
|||
) |
|||
|
|||
// Option configure async closer.
|
|||
type Option func(*Closer) |
|||
|
|||
// WithHandleError configure async error handler.
|
|||
func WithHandleError(he func(error)) Option { |
|||
return func(async *Closer) { |
|||
async.handler = he |
|||
} |
|||
} |
|||
|
|||
// New create new closer with options.
|
|||
func New(opts ...Option) *Closer { |
|||
a := &Closer{} |
|||
|
|||
for _, o := range opts { |
|||
o(a) |
|||
} |
|||
|
|||
return a |
|||
} |
|||
|
|||
// Closer closer.
|
|||
type Closer struct { |
|||
sync.Mutex |
|||
once sync.Once |
|||
done chan struct{} |
|||
fnc []func() error |
|||
handler func(error) |
|||
} |
|||
|
|||
// Wait when done context or notify signals or close all.
|
|||
func (c *Closer) Wait(ctx context.Context, sig ...os.Signal) { |
|||
go func() { |
|||
ch := make(chan os.Signal, 1) |
|||
|
|||
if len(sig) > 0 { |
|||
signal.Notify(ch, sig...) |
|||
defer signal.Stop(ch) |
|||
} |
|||
select { |
|||
case <-ch: |
|||
case <-ctx.Done(): |
|||
} |
|||
|
|||
_ = c.Close() |
|||
}() |
|||
<-c.wait() |
|||
} |
|||
|
|||
func (c *Closer) wait() chan struct{} { |
|||
c.Lock() |
|||
if c.done == nil { |
|||
c.done = make(chan struct{}) |
|||
} |
|||
c.Unlock() |
|||
|
|||
return c.done |
|||
} |
|||
|
|||
// Add close functions.
|
|||
func (c *Closer) Add(f ...func() error) { |
|||
c.Lock() |
|||
c.fnc = append(c.fnc, f...) |
|||
c.Unlock() |
|||
} |
|||
|
|||
// Close close all closers async.
|
|||
func (c *Closer) Close() error { |
|||
c.once.Do(func() { |
|||
defer close(c.wait()) |
|||
c.Lock() |
|||
eh := func(error) {} |
|||
if c.handler != nil { |
|||
eh = c.handler |
|||
} |
|||
funcs := c.fnc |
|||
c.fnc = nil |
|||
c.Unlock() |
|||
|
|||
errs := make(chan error, len(funcs)) |
|||
for _, f := range funcs { |
|||
go func(f func() error) { |
|||
errs <- f() |
|||
}(f) |
|||
} |
|||
for i := 0; i < cap(errs); i++ { |
|||
if err := <-errs; err != nil { |
|||
eh(err) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (c *Closer) SetErrHandler(e func(error)) { |
|||
c.Lock() |
|||
c.handler = e |
|||
c.Unlock() |
|||
} |
@ -0,0 +1,70 @@ |
|||
package async_test |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"syscall" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/async" |
|||
"gitoa.ru/go-4devs/closer/test" |
|||
) |
|||
|
|||
func ExampleCloser_Close() { |
|||
cl := async.New() |
|||
|
|||
cl.Add(func() error { |
|||
fmt.Print("do some close") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.Close() |
|||
// Output: do some close
|
|||
} |
|||
|
|||
func ExampleCloser_Wait_cancelContext() { |
|||
cl := async.New() |
|||
|
|||
ctx, cancel := context.WithTimeout(context.TODO(), time.Microsecond) |
|||
defer cancel() |
|||
|
|||
cl.Add(func() error { |
|||
fmt.Print("do some close with cancel context") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.Wait(ctx) |
|||
// Output: do some close with cancel context
|
|||
} |
|||
|
|||
func ExampleWithHandleError() { |
|||
cl := async.New(async.WithHandleError(func(err error) { |
|||
fmt.Printf("logged err:%s", err) |
|||
})) |
|||
|
|||
cl.Add(func() error { |
|||
return test.ErrClose |
|||
}) |
|||
|
|||
cl.Close() |
|||
// Output: logged err:some error
|
|||
} |
|||
|
|||
func ExampleCloser_Wait_syscall() { |
|||
time.AfterFunc(time.Millisecond, func() { |
|||
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) |
|||
}) |
|||
|
|||
cl := async.New() |
|||
|
|||
cl.Add(func() error { |
|||
fmt.Print("do some close with SIGTERM") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.Wait(context.TODO(), syscall.SIGTERM) |
|||
// Output: do some close with SIGTERM
|
|||
} |
@ -0,0 +1,95 @@ |
|||
package async_test |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"sync/atomic" |
|||
"syscall" |
|||
"testing" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/async" |
|||
"gitoa.ru/go-4devs/closer/test" |
|||
) |
|||
|
|||
func TestAsyncClose(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
c := async.Closer{} |
|||
closedFn := &test.Closed{} |
|||
c.Add(closedFn.CloseFnc("one", 0)) |
|||
test.RequireNil(t, c.Close()) |
|||
closedFn.TestLen(t, 1) |
|||
|
|||
c.Wait(context.Background()) |
|||
test.RequireNil(t, c.Close()) |
|||
closedFn.TestLen(t, 1) |
|||
|
|||
as := async.New(async.WithHandleError(func(e error) { |
|||
if !errors.Is(e, test.ErrClose) { |
|||
t.Fatalf("expect: %s, got:%s", test.ErrClose, e) |
|||
} |
|||
})) |
|||
as.Add(closedFn.CloseFnc("two", 0), func() error { |
|||
test.RequireNil(t, closedFn.CloseFnc("two", 0)()) |
|||
|
|||
return test.ErrClose |
|||
}) |
|||
test.RequireNil(t, as.Close()) |
|||
closedFn.TestLen(t, 3) |
|||
|
|||
c = async.Closer{} |
|||
closedFn = &test.Closed{} |
|||
c.Add( |
|||
closedFn.CloseFnc("one", time.Millisecond/2), |
|||
closedFn.CloseFnc("two", time.Microsecond), |
|||
closedFn.CloseFnc("three", time.Millisecond/5)) |
|||
test.RequireNil(t, c.Close()) |
|||
|
|||
closedFn.TestLen(t, 3) |
|||
} |
|||
|
|||
func TestAsyncWait_Timeout(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
c := &async.Closer{} |
|||
|
|||
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond) |
|||
defer cancel() |
|||
|
|||
var cnt int32 |
|||
|
|||
go func() { |
|||
c.Wait(ctx) |
|||
atomic.AddInt32(&cnt, 1) |
|||
}() |
|||
|
|||
cl := func() error { |
|||
atomic.AddInt32(&cnt, 1) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
c.Add(cl) |
|||
c.Wait(context.Background()) |
|||
|
|||
if atomic.LoadInt32(&cnt) != 1 { |
|||
t.Fail() |
|||
} |
|||
} |
|||
|
|||
func TestAsyncWait_Syscall(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
c := async.New() |
|||
cl := &test.Closed{} |
|||
|
|||
time.AfterFunc(time.Second, func() { |
|||
test.RequireNil(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM)) |
|||
}) |
|||
|
|||
c.Add(cl.CloseFnc("one", 0), cl.CloseFnc("one", 0)) |
|||
c.Wait(context.Background(), syscall.SIGTERM, syscall.SIGINT) |
|||
|
|||
cl.TestLen(t, 2) |
|||
} |
@ -0,0 +1,53 @@ |
|||
package closer |
|||
|
|||
import ( |
|||
"context" |
|||
"os" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/priority" |
|||
) |
|||
|
|||
// nolint: gochecknoglobals
|
|||
var closer = &priority.Closer{} |
|||
|
|||
// SetTimeout before close func.
|
|||
func SetTimeout(t time.Duration) { |
|||
closer.SetTimeout(t) |
|||
} |
|||
|
|||
// SetErrHandler before close func.
|
|||
func SetErrHandler(eh func(error)) { |
|||
closer.SetErrHandler(eh) |
|||
} |
|||
|
|||
// Add add closed func.
|
|||
func Add(f ...func() error) { |
|||
closer.Add(f...) |
|||
} |
|||
|
|||
// AddByPriority add close by priority 255 its close first 0 - last.
|
|||
func AddByPriority(priority uint8, f ...func() error) { |
|||
closer.AddByPriority(priority, f...) |
|||
} |
|||
|
|||
// AddLast add closer which execute at the end.
|
|||
func AddLast(f ...func() error) { |
|||
closer.AddLast(f...) |
|||
} |
|||
|
|||
// AddFirst add closer which execute at the begin.
|
|||
func AddFirst(f ...func() error) { |
|||
closer.AddFirst(f...) |
|||
} |
|||
|
|||
// Close all func.
|
|||
// nolint: wrapcheck
|
|||
func Close() error { |
|||
return closer.Close() |
|||
} |
|||
|
|||
// Wait cancel ctx or signal.
|
|||
func Wait(ctx context.Context, sig ...os.Signal) { |
|||
closer.Wait(ctx, sig...) |
|||
} |
@ -0,0 +1,125 @@ |
|||
package closer_test |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"log" |
|||
"syscall" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer" |
|||
"gitoa.ru/go-4devs/closer/priority" |
|||
"gitoa.ru/go-4devs/closer/test" |
|||
) |
|||
|
|||
func ExampleWait() { |
|||
ctx, cancel := context.WithTimeout(context.TODO(), time.Microsecond) |
|||
defer cancel() |
|||
|
|||
closer.SetErrHandler(func(err error) { |
|||
fmt.Print("\nlogged err:", err.Error()) |
|||
}) |
|||
|
|||
closer.Add(func() error { |
|||
fmt.Print("do some close. ") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddFirst(func() error { |
|||
fmt.Print("close http server for example. ") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddLast(func() error { |
|||
fmt.Print("close db for example.") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddByPriority(priority.Last-1, func() error { |
|||
return test.ErrClose |
|||
}) |
|||
|
|||
closer.Wait(ctx) |
|||
// Output:
|
|||
// close http server for example. do some close. close db for example.
|
|||
// logged err:some error
|
|||
} |
|||
|
|||
func ExampleClose() { |
|||
closer.Add(func() error { |
|||
// normal stop.
|
|||
|
|||
return nil |
|||
}, func() error { |
|||
time.Sleep(time.Millisecond) |
|||
// long normal stop.
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddFirst(func() error { |
|||
// first stop.
|
|||
|
|||
return nil |
|||
}) |
|||
closer.AddLast(func() error { |
|||
// last stop.
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddByPriority(priority.First+1, func() error { |
|||
// run before first.
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.AddByPriority(priority.Normal-1, func() error { |
|||
// run after normal.
|
|||
|
|||
return nil |
|||
}) |
|||
closer.Close() |
|||
} |
|||
|
|||
func ExampleWait_cancelContext() { |
|||
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond) |
|||
defer cancel() |
|||
|
|||
closer.Add(func() error { |
|||
// do some close with cancel context
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.Wait(ctx) |
|||
} |
|||
|
|||
func ExampleSetErrHandler() { |
|||
closer.SetErrHandler(func(err error) { |
|||
log.Print("logged err:", err.Error()) |
|||
}) |
|||
|
|||
closer.Add(func() error { |
|||
return test.ErrClose |
|||
}) |
|||
|
|||
closer.Close() |
|||
} |
|||
|
|||
func ExampleWait_syscall() { |
|||
time.AfterFunc(time.Millisecond, func() { |
|||
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) |
|||
}) |
|||
|
|||
closer.Add(func() error { |
|||
// do some close with SIGTERM
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
closer.Wait(context.TODO(), syscall.SIGTERM) |
|||
} |
@ -0,0 +1,3 @@ |
|||
module gitoa.ru/go-4devs/closer |
|||
|
|||
go 1.16 |
@ -0,0 +1,220 @@ |
|||
package priority |
|||
|
|||
import ( |
|||
"context" |
|||
"log" |
|||
"os" |
|||
"os/signal" |
|||
"sort" |
|||
"sync" |
|||
"time" |
|||
) |
|||
|
|||
// defaults.
|
|||
const ( |
|||
First = 250 |
|||
Normal = 100 |
|||
Last = 5 |
|||
) |
|||
|
|||
// Option configure priority closer.
|
|||
type Option func(*Closer) |
|||
|
|||
// WithHandleError configure priority error handler.
|
|||
func WithHandleError(he func(error)) Option { |
|||
return func(async *Closer) { |
|||
async.handler = he |
|||
} |
|||
} |
|||
|
|||
// WithTimeout configure priority error handler.
|
|||
func WithTimeout(d time.Duration) Option { |
|||
return func(async *Closer) { |
|||
async.timeout = d |
|||
} |
|||
} |
|||
|
|||
// New create new closer with options.
|
|||
func New(opts ...Option) *Closer { |
|||
a := &Closer{} |
|||
|
|||
for _, o := range opts { |
|||
o(a) |
|||
} |
|||
|
|||
return a |
|||
} |
|||
|
|||
// Closer close by priority.
|
|||
type Closer struct { |
|||
sync.Mutex |
|||
fnc map[uint8][]func() error |
|||
priority prioritySlice |
|||
sumPriority uint |
|||
once sync.Once |
|||
handler func(error) |
|||
timeout time.Duration |
|||
len int |
|||
done chan struct{} |
|||
} |
|||
|
|||
// Add adds closed func by normal priority.
|
|||
func (c *Closer) Add(f ...func() error) { |
|||
c.AddByPriority(Normal, f...) |
|||
} |
|||
|
|||
// AddLast add closer which execute at the end.
|
|||
func (c *Closer) AddLast(f ...func() error) { |
|||
c.AddByPriority(Last, f...) |
|||
} |
|||
|
|||
// AddFirst add closer which execute at the begin.
|
|||
func (c *Closer) AddFirst(f ...func() error) { |
|||
c.AddByPriority(First, f...) |
|||
} |
|||
|
|||
// AddByPriority add close by priority 255 its close first 0 - last.
|
|||
func (c *Closer) AddByPriority(priority uint8, f ...func() error) { |
|||
if len(f) == 0 { |
|||
return |
|||
} |
|||
|
|||
c.Lock() |
|||
if c.fnc == nil { |
|||
c.fnc = make(map[uint8][]func() error) |
|||
} |
|||
|
|||
if len(c.fnc[priority]) == 0 { |
|||
c.priority = append(c.priority, priority) |
|||
sort.Sort(c.priority) |
|||
c.sumPriority += uint(priority) |
|||
} |
|||
|
|||
c.len += len(f) |
|||
c.fnc[priority] = append(c.fnc[priority], f...) |
|||
c.Unlock() |
|||
} |
|||
|
|||
// Wait wait signal or cancel context.
|
|||
func (c *Closer) Wait(ctx context.Context, sig ...os.Signal) { |
|||
go func() { |
|||
ch := make(chan os.Signal, 1) |
|||
|
|||
if len(sig) > 0 { |
|||
signal.Notify(ch, sig...) |
|||
defer signal.Stop(ch) |
|||
} |
|||
select { |
|||
case <-ch: |
|||
case <-ctx.Done(): |
|||
} |
|||
|
|||
_ = c.Close() |
|||
}() |
|||
<-c.wait() |
|||
} |
|||
|
|||
// Close closes all func by priority.
|
|||
func (c *Closer) Close() error { |
|||
c.once.Do(func() { |
|||
start := time.Now() |
|||
c.Lock() |
|||
eh := func(err error) { |
|||
log.Print(err) |
|||
} |
|||
|
|||
if c.handler != nil { |
|||
eh = c.handler |
|||
} |
|||
|
|||
w := &wait{ |
|||
timeout: c.timeout, |
|||
priority: c.sumPriority, |
|||
} |
|||
funcs := c.fnc |
|||
c.fnc = nil |
|||
c.Unlock() |
|||
errs := make(chan error, c.len) |
|||
go func() { |
|||
defer close(c.wait()) |
|||
for i := 0; i < cap(errs); i++ { |
|||
if err := <-errs; err != nil { |
|||
eh(err) |
|||
} |
|||
} |
|||
}() |
|||
for _, p := range c.priority { |
|||
var wg sync.WaitGroup |
|||
wg.Add(len(funcs[p])) |
|||
for _, f := range funcs[p] { |
|||
go func(f func() error) { |
|||
errs <- f() |
|||
wg.Done() |
|||
}(f) |
|||
} |
|||
w.done(start, uint(p), &wg) |
|||
} |
|||
<-c.wait() |
|||
}) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// SetTimeout before close func.
|
|||
func (c *Closer) SetTimeout(t time.Duration) { |
|||
c.Lock() |
|||
c.timeout = t |
|||
c.Unlock() |
|||
} |
|||
|
|||
// SetErrHandler before close func.
|
|||
func (c *Closer) SetErrHandler(e func(error)) { |
|||
c.Lock() |
|||
c.handler = e |
|||
c.Unlock() |
|||
} |
|||
|
|||
func (c *Closer) wait() chan struct{} { |
|||
c.Lock() |
|||
if c.done == nil { |
|||
c.done = make(chan struct{}) |
|||
} |
|||
c.Unlock() |
|||
|
|||
return c.done |
|||
} |
|||
|
|||
type wait struct { |
|||
timeout time.Duration |
|||
priority uint |
|||
} |
|||
|
|||
func (w *wait) done(start time.Time, priority uint, wg *sync.WaitGroup) { |
|||
done := make(chan struct{}) |
|||
|
|||
go func() { |
|||
defer close(done) |
|||
wg.Wait() |
|||
}() |
|||
select { |
|||
case <-w.after(start, priority): |
|||
case <-done: |
|||
} |
|||
} |
|||
|
|||
func (w *wait) after(start time.Time, priority uint) <-chan time.Time { |
|||
timeout := (w.timeout - time.Since(start)) / time.Duration(w.priority) * time.Duration(priority) |
|||
w.priority -= priority |
|||
|
|||
if timeout <= 0 { |
|||
return nil |
|||
} |
|||
|
|||
return time.After(timeout) |
|||
} |
|||
|
|||
type prioritySlice []uint8 |
|||
|
|||
func (p prioritySlice) Len() int { return len(p) } |
|||
func (p prioritySlice) Less(i, j int) bool { return p[i] > p[j] } |
|||
func (p prioritySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
@ -0,0 +1,112 @@ |
|||
package priority_test |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"io" |
|||
"net" |
|||
"net/http" |
|||
"syscall" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/priority" |
|||
) |
|||
|
|||
type closer struct { |
|||
msg string |
|||
} |
|||
|
|||
// nolint: forbidigo
|
|||
func (c *closer) Close() error { |
|||
fmt.Print(c.msg) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func OpenDB() io.Closer { |
|||
return &closer{msg: "close db. "} |
|||
} |
|||
|
|||
func NewConsumer() io.Closer { |
|||
return &closer{msg: "close consumer. "} |
|||
} |
|||
|
|||
func ExampleCloser_Wait() { |
|||
time.AfterFunc(time.Second/2, func() { |
|||
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) |
|||
}) |
|||
|
|||
ctx := context.Background() |
|||
|
|||
cl := priority.New( |
|||
priority.WithTimeout(time.Second*5), |
|||
priority.WithHandleError(func(e error) { |
|||
fmt.Print(e) |
|||
}), |
|||
) |
|||
db := OpenDB() |
|||
cl.AddLast(db.Close) |
|||
|
|||
// your consumer events
|
|||
consumer := NewConsumer() |
|||
cl.AddFirst(consumer.Close) |
|||
|
|||
s := http.Server{} |
|||
// your http listeners
|
|||
listener, _ := net.Listen("tcp", "127.0.0.1:0") |
|||
|
|||
go func() { |
|||
if err := s.Serve(listener); err != nil { |
|||
_ = cl.Close() |
|||
} |
|||
}() |
|||
|
|||
cl.Add(func() error { |
|||
fmt.Print("stop server. ") |
|||
|
|||
return s.Shutdown(ctx) |
|||
}) |
|||
|
|||
cl.Wait(ctx, syscall.SIGTERM, syscall.SIGINT) |
|||
// Output: close consumer. stop server. close db.
|
|||
} |
|||
|
|||
func ExampleCloser_Close() { |
|||
cl := priority.New() |
|||
|
|||
cl.Add(func() error { |
|||
fmt.Print("normal stop. ") |
|||
|
|||
return nil |
|||
}, func() error { |
|||
time.Sleep(time.Millisecond) |
|||
fmt.Print("long normal stop. ") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.AddFirst(func() error { |
|||
fmt.Print("first stop. ") |
|||
|
|||
return nil |
|||
}) |
|||
cl.AddLast(func() error { |
|||
fmt.Print("last stop. ") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.AddByPriority(priority.First+1, func() error { |
|||
fmt.Print("run before first. ") |
|||
|
|||
return nil |
|||
}) |
|||
cl.AddByPriority(priority.Normal-1, func() error { |
|||
fmt.Print("run after normal. ") |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
cl.Close() |
|||
// Output: run before first. first stop. normal stop. long normal stop. run after normal. last stop.
|
|||
} |
@ -0,0 +1,63 @@ |
|||
package priority_test |
|||
|
|||
import ( |
|||
"context" |
|||
"syscall" |
|||
"testing" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/priority" |
|||
"gitoa.ru/go-4devs/closer/test" |
|||
) |
|||
|
|||
func TestPriority_Close(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
cl := priority.Closer{} |
|||
closed := &test.Closed{} |
|||
|
|||
go cl.Wait(context.Background()) |
|||
|
|||
cl.Add() |
|||
cl.Add(closed.CloseFnc("one", time.Microsecond)) |
|||
cl.AddLast(closed.CloseFnc("last", time.Microsecond)) |
|||
cl.AddFirst(closed.CloseFnc("first", time.Millisecond)) |
|||
cl.AddByPriority(priority.Normal, closed.CloseFnc("one", time.Microsecond), closed.CloseFnc("one", time.Microsecond)) |
|||
test.RequireNil(t, cl.Close()) |
|||
closed.TestEqual(t, []string{"first", "one", "one", "one", "last"}) |
|||
} |
|||
|
|||
func TestPriority_Wait_Timeout(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
closed := &test.Closed{} |
|||
|
|||
cl := priority.New(priority.WithTimeout(time.Second/5), priority.WithHandleError(func(error) {})) |
|||
cl.Add(closed.CloseFnc("one", 0)) |
|||
cl.AddByPriority(priority.First, closed.CloseFnc("first", time.Second)) |
|||
cl.AddByPriority(priority.Last, closed.CloseFnc("last", 0)) |
|||
cl.AddLast(func() error { |
|||
return test.ErrClose |
|||
}) |
|||
|
|||
time.AfterFunc(time.Second/3, func() { |
|||
test.RequireNil(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM)) |
|||
}) |
|||
cl.Wait(context.Background(), syscall.SIGTERM) |
|||
closed.TestEqual(t, []string{"one", "last", "first"}) |
|||
} |
|||
|
|||
func TestPriority_Wait(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx, cancel := context.WithTimeout(context.Background(), time.Second) |
|||
defer cancel() |
|||
|
|||
cl := priority.New(priority.WithHandleError(func(err error) { |
|||
test.RequireError(t, err, test.ErrClose) |
|||
})) |
|||
cl.AddLast(func() error { |
|||
return test.ErrClose |
|||
}) |
|||
cl.Wait(ctx) |
|||
} |
@ -0,0 +1,19 @@ |
|||
MIT License Copyright (c) 2020 4devs |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice (including the next |
|||
paragraph) shall be included in all copies or substantial portions of the |
|||
Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS |
|||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF |
|||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,3 @@ |
|||
module gitoa.ru/go-4devs/closer/routine |
|||
|
|||
go 1.16 |
@ -0,0 +1,55 @@ |
|||
package routine |
|||
|
|||
import "sync" |
|||
|
|||
// nolint: gochecknoglobals
|
|||
var global = &WaitGroup{} |
|||
|
|||
// Go run routine and add wait group.
|
|||
func Go(fnc func()) { |
|||
global.Go(fnc) |
|||
} |
|||
|
|||
// Run run routines and add wait group.
|
|||
func Run(fnc ...func()) { |
|||
global.Run(fnc...) |
|||
} |
|||
|
|||
// Close global routines.
|
|||
func Close() error { |
|||
return global.Close() |
|||
} |
|||
|
|||
// Wait wait all go routines.
|
|||
func Wait() { |
|||
global.Wait() |
|||
} |
|||
|
|||
// WaitGroup run func and wait when done.
|
|||
type WaitGroup struct { |
|||
sync.WaitGroup |
|||
} |
|||
|
|||
// Close wait all routines and implement Closer.
|
|||
func (wg *WaitGroup) Close() error { |
|||
wg.Wait() |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// Go add wait group to routines.
|
|||
func (wg *WaitGroup) Go(fnc func()) { |
|||
wg.Run(fnc) |
|||
} |
|||
|
|||
// Run functions in routine and add wait group.
|
|||
func (wg *WaitGroup) Run(fnc ...func()) { |
|||
wg.Add(len(fnc)) |
|||
|
|||
for i := range fnc { |
|||
go func(i int) { |
|||
defer wg.Done() |
|||
fnc[i]() |
|||
}(i) |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
package routine_test |
|||
|
|||
import ( |
|||
"fmt" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/routine" |
|||
) |
|||
|
|||
func ExampleGo() { |
|||
defer routine.Close() |
|||
routine.Go(func() { |
|||
time.Sleep(time.Microsecond) |
|||
fmt.Print("do some job") |
|||
}) |
|||
|
|||
// Output: do some job
|
|||
} |
|||
|
|||
func ExampleRun() { |
|||
defer routine.Close() |
|||
|
|||
routine.Run(func() { |
|||
time.Sleep(time.Microsecond) |
|||
fmt.Print("do some job. ") |
|||
}, func() { |
|||
fmt.Print("fast job in goroutine. ") |
|||
}) |
|||
|
|||
// Output: fast job in goroutine. do some job.
|
|||
} |
@ -0,0 +1,69 @@ |
|||
package routine_test |
|||
|
|||
import ( |
|||
"sync" |
|||
"testing" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/closer/routine" |
|||
) |
|||
|
|||
func equal(t *testing.T, exp, res int) { |
|||
t.Helper() |
|||
|
|||
if exp != res { |
|||
t.Fail() |
|||
} |
|||
} |
|||
|
|||
func TestGo(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
dt := make(map[string]int) |
|||
|
|||
var mu sync.Mutex |
|||
|
|||
fnc := func(name string) func() { |
|||
return func() { |
|||
time.Sleep(time.Millisecond) |
|||
mu.Lock() |
|||
dt[name]++ |
|||
mu.Unlock() |
|||
} |
|||
} |
|||
|
|||
equal(t, 0, dt["once"]) |
|||
|
|||
routine.Go(fnc("once")) |
|||
routine.Run(fnc("twice"), fnc("twice")) |
|||
routine.Wait() |
|||
|
|||
equal(t, 1, dt["once"]) |
|||
equal(t, 2, dt["twice"]) |
|||
} |
|||
|
|||
func TestClose(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
dt := make(map[string]int) |
|||
|
|||
var mu sync.Mutex |
|||
|
|||
fnc := func(name string) func() { |
|||
return func() { |
|||
time.Sleep(time.Millisecond) |
|||
mu.Lock() |
|||
dt[name]++ |
|||
mu.Unlock() |
|||
} |
|||
} |
|||
routine.Go(fnc("once")) |
|||
routine.Run(fnc("twice"), fnc("twice")) |
|||
|
|||
if err := routine.Close(); err != nil { |
|||
t.Fail() |
|||
} |
|||
|
|||
equal(t, 1, dt["once"]) |
|||
equal(t, 2, dt["twice"]) |
|||
} |
@ -0,0 +1,15 @@ |
|||
package closer |
|||
|
|||
import ( |
|||
"context" |
|||
"time" |
|||
) |
|||
|
|||
func Shutdown(fnc func(context.Context) error, timeout time.Duration) func() error { |
|||
return func() error { |
|||
ctx, cancel := context.WithTimeout(context.Background(), timeout) |
|||
defer cancel() |
|||
|
|||
return fnc(ctx) |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"errors" |
|||
"sync" |
|||
"testing" |
|||
"time" |
|||
) |
|||
|
|||
var ErrClose = errors.New("some error") |
|||
|
|||
type Closed struct { |
|||
mu sync.Mutex |
|||
d []string |
|||
} |
|||
|
|||
func (c *Closed) TestLen(t *testing.T, exp int) { |
|||
if len(c.d) != exp { |
|||
t.Fail() |
|||
} |
|||
} |
|||
|
|||
func (c *Closed) TestEqual(t *testing.T, exp []string) { |
|||
if len(c.d) != len(exp) { |
|||
t.Fail() |
|||
} |
|||
|
|||
for i := range exp { |
|||
if exp[i] != c.d[i] { |
|||
t.Fail() |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (c *Closed) CloseFnc(name string, sleep time.Duration) func() error { |
|||
return func() error { |
|||
time.Sleep(sleep) |
|||
c.mu.Lock() |
|||
c.d = append(c.d, name) |
|||
c.mu.Unlock() |
|||
|
|||
return nil |
|||
} |
|||
} |
|||
|
|||
func RequireNil(t *testing.T, exp interface{}) { |
|||
t.Helper() |
|||
|
|||
if exp != nil { |
|||
t.Fatalf("expected nil, got %v", exp) |
|||
} |
|||
} |
|||
|
|||
func RequireError(t *testing.T, exp, target error) { |
|||
t.Helper() |
|||
|
|||
if !errors.Is(exp, target) { |
|||
t.Fatalf("expected %s, got %s", exp, target) |
|||
} |
|||
} |
Loading…
Reference in new issue