Skip to content

Commit

Permalink
Merge pull request #85 from nabbar/issue_84
Browse files Browse the repository at this point in the history
Issue 84: race detection on httpserver
  • Loading branch information
Nicolas JUHEL authored Feb 25, 2021
2 parents a7348db + ccf68d2 commit 838b1c0
Show file tree
Hide file tree
Showing 5 changed files with 502 additions and 222 deletions.
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/PuerkitoBio/goquery v1.6.1 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/aokoli/goutils v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.2.0
Expand Down Expand Up @@ -56,14 +56,14 @@ require (
github.com/vbauerster/mpb/v5 v5.4.0
github.com/xanzy/go-gitlab v0.44.0
github.com/xhit/go-simple-mail v2.2.2+incompatible
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210225080010-8e9945a5478f // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
10 changes: 8 additions & 2 deletions httpserver/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type PoolServer interface {
Filter(field FieldType, pattern, regex string) PoolServer

IsRunning(asLeast bool) bool
WaitNotify(ctx context.Context)
WaitNotify(ctx context.Context, cancel context.CancelFunc)

Listen(handler http.Handler) liberr.Error
Restart()
Expand Down Expand Up @@ -310,7 +310,7 @@ func (p pool) IsRunning(atLeast bool) bool {
return r
}

func (p pool) WaitNotify(ctx context.Context) {
func (p pool) WaitNotify(ctx context.Context, cancel context.CancelFunc) {
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
Expand All @@ -321,8 +321,14 @@ func (p pool) WaitNotify(ctx context.Context) {
select {
case <-quit:
p.Shutdown()
if cancel != nil {
cancel()
}
case <-ctx.Done():
p.Shutdown()
if cancel != nil {
cancel()
}
}
}

Expand Down
313 changes: 313 additions & 0 deletions httpserver/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* 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 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.
*
*
**********************************************************************************************************************/

package httpserver

import (
"context"
"errors"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"

liberr "github.com/nabbar/golib/errors"
liblog "github.com/nabbar/golib/logger"
"golang.org/x/net/http2"
)

type srvRun struct {
run *atomic.Value
snm string
srv *http.Server
ctx context.Context
cnl context.CancelFunc
}

type run interface {
IsRunning() bool
WaitNotify()
Listen(cfg *ServerConfig, handler http.Handler) liberr.Error
Restart(cfg *ServerConfig)
Shutdown()
}

func newRun() run {
return &srvRun{
run: new(atomic.Value),
srv: nil,
}
}

func (s *srvRun) getRunning() bool {
if s.run == nil {
return false
} else if i := s.run.Load(); i == nil {
return false
} else if b, ok := i.(bool); !ok {
return false
} else {
return b
}
}

func (s *srvRun) setRunning(state bool) {
s.run.Store(state)
}

func (s *srvRun) IsRunning() bool {
return s.getRunning()
}

func (s *srvRun) WaitNotify() {
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT)
signal.Notify(quit, syscall.SIGTERM)
signal.Notify(quit, syscall.SIGQUIT)

select {
case <-quit:
s.Shutdown()
case <-s.ctx.Done():
s.Shutdown()
}
}

func (s *srvRun) Merge(srv Server) bool {
panic("implement me")
}

func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
ssl, err := cfg.GetTLS()
if err != nil {
return err
}

bind := cfg.GetListen().Host
name := cfg.Name
if name == "" {
name = bind
}

srv := &http.Server{
Addr: cfg.GetListen().Host,
ErrorLog: liblog.GetLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds, "[http/http2 server '%s']", name),
}

if cfg.ReadTimeout > 0 {
srv.ReadTimeout = cfg.ReadTimeout
}

if cfg.ReadHeaderTimeout > 0 {
srv.ReadHeaderTimeout = cfg.ReadHeaderTimeout
}

if cfg.WriteTimeout > 0 {
srv.WriteTimeout = cfg.WriteTimeout
}

if cfg.MaxHeaderBytes > 0 {
srv.MaxHeaderBytes = cfg.MaxHeaderBytes
}

if cfg.IdleTimeout > 0 {
srv.IdleTimeout = cfg.IdleTimeout
}

if ssl.LenCertificatePair() > 0 {
srv.TLSConfig = ssl.TlsConfig("")
}

if handler != nil {
srv.Handler = handler
} else if s.srv != nil {
srv.Handler = s.srv.Handler
}

s2 := &http2.Server{}

if cfg.MaxHandlers > 0 {
s2.MaxHandlers = cfg.MaxHandlers
}

if cfg.MaxConcurrentStreams > 0 {
s2.MaxConcurrentStreams = cfg.MaxConcurrentStreams
}

if cfg.PermitProhibitedCipherSuites {
s2.PermitProhibitedCipherSuites = true
}

if cfg.IdleTimeout > 0 {
s2.IdleTimeout = cfg.IdleTimeout
}

if cfg.MaxUploadBufferPerConnection > 0 {
s2.MaxUploadBufferPerConnection = cfg.MaxUploadBufferPerConnection
}

if cfg.MaxUploadBufferPerStream > 0 {
s2.MaxUploadBufferPerStream = cfg.MaxUploadBufferPerStream
}

if e := http2.ConfigureServer(srv, s2); e != nil {
return ErrorHTTP2Configure.ErrorParent(e)
}

if s.IsRunning() {
s.Shutdown()
}

for i := 0; i < 5; i++ {
if e := s.PortInUse(cfg.Listen); e != nil {
s.Shutdown()
} else {
break
}
}

if s.ctx != nil && s.ctx.Err() == nil && s.cnl != nil {
s.cnl()
s.ctx = nil
s.cnl = nil
}

s.ctx, s.cnl = context.WithCancel(cfg.getContext())
s.snm = name
s.srv = srv

go func(name, host string) {

defer func() {
if s.ctx != nil && s.cnl != nil && s.ctx.Err() == nil {
s.cnl()
}
s.setRunning(false)
}()

s.srv.BaseContext = func(listener net.Listener) context.Context {
return s.ctx
}

var err error

if ssl.LenCertificatePair() > 0 {
liblog.InfoLevel.Logf("TLS Server '%s' is starting with bindable: %s", name, host)

s.setRunning(true)
err = s.srv.ListenAndServeTLS("", "")
} else {
liblog.InfoLevel.Logf("Server '%s' is starting with bindable: %s", name, host)

s.setRunning(true)
err = s.srv.ListenAndServe()
}

if err != nil && s.ctx.Err() != nil && s.ctx.Err().Error() == err.Error() {
return
} else if err != nil && errors.Is(err, http.ErrServerClosed) {
return
} else if err != nil {
liblog.ErrorLevel.LogErrorCtxf(liblog.NilLevel, "Listen Server '%s'", err, name)
}
}(name, bind)

return nil
}

func (s *srvRun) Restart(cfg *ServerConfig) {
_ = s.Listen(cfg, nil)
}

func (s *srvRun) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), timeoutShutdown)

defer func() {
cancel()

if s.srv != nil {
_ = s.srv.Close()
}

s.setRunning(false)
}()

liblog.InfoLevel.Logf("Shutdown Server '%s'...", s.snm)

if s.cnl != nil && s.ctx != nil && s.ctx.Err() == nil {
s.cnl()
}

if s.srv != nil {
err := s.srv.Shutdown(ctx)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
liblog.ErrorLevel.Logf("Shutdown Server '%s' Error: %v", s.snm, err)
}
}
}

func (s *srvRun) PortInUse(listen string) liberr.Error {
var (
dia = net.Dialer{}
con net.Conn
err error
ctx context.Context
cnl context.CancelFunc
)

defer func() {
if cnl != nil {
cnl()
}
if con != nil {
_ = con.Close()
}
}()

ctx, cnl = context.WithTimeout(context.TODO(), 2*time.Second)
con, err = dia.DialContext(ctx, "tcp", listen)

if con != nil {
_ = con.Close()
con = nil
}

cnl()
cnl = nil

if err != nil {
return nil
}

return ErrorPortUse.Error(nil)
}
Loading

0 comments on commit 838b1c0

Please sign in to comment.