Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to support older go 1.18 #8

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module github.com/gin-contrib/graceful

go 1.20
go 1.18

require (
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
golang.org/x/sync v0.4.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
Expand Down
85 changes: 58 additions & 27 deletions graceful.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"

"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)

// Graceful is a wrapper around a gin.Engine that provides graceful shutdown
Expand All @@ -16,6 +17,7 @@ type Graceful struct {

started context.Context
stop context.CancelFunc
err chan error

lock sync.Mutex
servers []*http.Server
Expand Down Expand Up @@ -104,40 +106,37 @@ func (g *Graceful) RunListener(listener net.Listener) error {
// :8080 if none are configured) and starts listening and serving HTTP requests. If the passed
// context is canceled, the server is gracefully shut down
func (g *Graceful) RunWithContext(ctx context.Context) error {
var wg sync.WaitGroup
if err := g.ensureAtLeastDefaultServer(); err != nil {
return err
}

ctx, cancel := context.WithCancelCause(ctx)
ctx, cancel := context.WithCancel(ctx)
go func() {
<-ctx.Done()
_ = g.Shutdown(ctx)
}()
defer cancel(nil)
defer cancel()

g.lock.Lock()
eg := errgroup.Group{}

if len(g.listenAndServe) == 0 {
if err := g.apply(WithAddr(":8080")); err != nil {
return err
}
}
g.lock.Lock()

for _, srv := range g.listenAndServe {
wg.Add(1)
go func(srv listenAndServe) {
defer wg.Done()
if err := srv(); err != nil && err != http.ErrServerClosed {
cancel(err)
_ = g.Shutdown(ctx)
safeCopy := srv
eg.Go(func() error {
if err := safeCopy(); err != nil && err != http.ErrServerClosed {
return err
}
}(srv)
return nil
})
}

g.lock.Unlock()

wg.Wait()
if ctx.Err() != nil {
return context.Cause(ctx)
if err := waitWithContext(ctx, &eg); err != nil {
return err
}
return nil
return g.Shutdown(ctx)
}

// Shutdown gracefully shuts down the server without interrupting any active connections.
Expand Down Expand Up @@ -167,11 +166,13 @@ func (g *Graceful) Start() error {
return ErrAlreadyStarted
}

ctxStarted, cancel := context.WithCancelCause(context.Background())
g.err = make(chan error)
ctxStarted, cancel := context.WithCancel(context.Background())
ctx, cancelStop := context.WithCancel(context.Background())
go func() {
err := g.RunWithContext(ctx)
cancel(err)
cancel()
g.err <- err
}()

g.stop = cancelStop
Expand All @@ -183,30 +184,36 @@ func (g *Graceful) Start() error {
// Stop will stop the Graceful instance previously started with Start. It
// will return once the instance has been stopped.
func (g *Graceful) Stop() error {
resetStartedState := func() (context.Context, context.CancelFunc, error) {
resetStartedState := func() (context.Context, context.CancelFunc, chan error, error) {
g.lock.Lock()
defer g.lock.Unlock()

if g.started == nil {
return nil, nil, ErrNotStarted
return nil, nil, nil, ErrNotStarted
}

stop := g.stop
started := g.started
chErr := g.err
g.stop = nil
g.started = nil

return started, stop, nil
return started, stop, chErr, nil
}
started, stop, err := resetStartedState()
started, stop, chErr, err := resetStartedState()
if err != nil {
return err
}

stop()
err = <-chErr
<-started.Done()

err = context.Cause(started)
if !errors.Is(err, context.Canceled) {
return err
}

err = started.Err()
if errors.Is(err, context.Canceled) {
err = nil
}
Expand Down Expand Up @@ -250,4 +257,28 @@ func (g *Graceful) appendHttpServer() *http.Server {
return srv
}

func (g *Graceful) ensureAtLeastDefaultServer() error {
g.lock.Lock()
defer g.lock.Unlock()

if len(g.listenAndServe) == 0 {
if err := g.apply(WithAddr(":8080")); err != nil {
return err
}
}
return nil
}

func waitWithContext(ctx context.Context, eg *errgroup.Group) error {
if err := eg.Wait(); err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}

func donothing() {}
Loading