Browse Source

first commit

master
andrey1s 3 years ago
commit
b5f9c60818
  1. BIN
      .DS_Store
  2. 25
      .drone.yml
  3. 17
      .gitignore
  4. 46
      .golangci.yml
  5. 19
      LICENSE
  6. 5
      README.md
  7. 109
      async/closer.go
  8. 70
      async/closer_example_test.go
  9. 95
      async/closer_test.go
  10. 53
      closer.go
  11. 125
      closer_example_test.go
  12. 3
      go.mod
  13. 0
      go.sum
  14. 220
      priority/closer.go
  15. 112
      priority/closer_example_test.go
  16. 63
      priority/closer_test.go
  17. 19
      routine/LICENSE
  18. 3
      routine/go.mod
  19. 55
      routine/runner.go
  20. 31
      routine/runner_examplte_test.go
  21. 69
      routine/runner_test.go
  22. 15
      shutdown.go
  23. 60
      test/closer.go

BIN
.DS_Store

Binary file not shown.

25
.drone.yml

@ -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

17
.gitignore

@ -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/

46
.golangci.yml

@ -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

19
LICENSE

@ -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.

5
README.md

@ -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)

109
async/closer.go

@ -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()
}

70
async/closer_example_test.go

@ -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
}

95
async/closer_test.go

@ -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)
}

53
closer.go

@ -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...)
}

125
closer_example_test.go

@ -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)
}

3
go.mod

@ -0,0 +1,3 @@
module gitoa.ru/go-4devs/closer
go 1.16

0
go.sum

220
priority/closer.go

@ -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] }

112
priority/closer_example_test.go

@ -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.
}

63
priority/closer_test.go

@ -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)
}

19
routine/LICENSE

@ -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.

3
routine/go.mod

@ -0,0 +1,3 @@
module gitoa.ru/go-4devs/closer/routine
go 1.16

55
routine/runner.go

@ -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)
}
}

31
routine/runner_examplte_test.go

@ -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.
}

69
routine/runner_test.go

@ -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"])
}

15
shutdown.go

@ -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)
}
}

60
test/closer.go

@ -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…
Cancel
Save