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