Package Errors:

- Add interface Errors to expose func for collection of errors

Package Server:
- Add runner packages for start/stop & ticker process launch in goroutines
- Runner Start/Stop : register a start and a stop function called into a managment instance of goroutines, expose package Server interface
- Runner Ticker : register a func periodycly called into a managment instance of goroutine with a time ticker, expose package server interface
- Add tools function to run x times a function or periodicly with a check function

Package HttpServer:
- Remove old run managment instance and use new Start&Stop Runner instead self runner
- Replace the poller in server start / stop with package server tools function
- Adjust code following this change
- Add in healthcheck a test of dialing to server binding network to perform the healthcheck
- Remove WaitNotify funct (cannot having multiple of waitnotify in same app) : keep waitnotify function in package config

Package Monitor:
- Remove old running system to use package server/runner/ticker
- Adjust code following

Package Cobra :
- Fix minor bug with writing configure
- Optimize code

Bump dependencies
This commit is contained in:
nabbar
2023-04-03 17:13:26 +02:00
parent 7ea47dd800
commit e047c986a6
31 changed files with 1367 additions and 545 deletions

View File

@@ -27,10 +27,10 @@
package cobra
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -87,46 +87,49 @@ func (c *cobra) ConfigureWriteConfig(basename string, defaultConfig func() io.Re
}
var (
fs *os.File
fs *os.File
ext string
buf io.Reader
nbr int64
err error
)
defer func() {
if fs != nil {
_ = fs.Close()
}
}()
ext = strings.ToLower(filepath.Ext(cfgFile))
buf, err := ioutil.ReadAll(defaultConfig())
if err != nil {
return err
}
if len(filepath.Ext(cfgFile)) > 0 && strings.ToLower(filepath.Ext(cfgFile)) != ".json" {
var mod = make(map[string]interface{}, 0)
err = json.Unmarshal(buf, &mod)
if err != nil {
switch ext {
case ".toml", ".tml":
if buf, err = c.jsonToToml(defaultConfig()); err != nil {
return err
}
switch strings.ToLower(filepath.Ext(cfgFile)) {
case ".toml":
buf, err = toml.Marshal(mod)
case ".yml", ".yaml":
buf, err = yaml.Marshal(mod)
default:
return fmt.Errorf("extension file '%s' not compatible", filepath.Ext(cfgFile))
case ".yaml", ".yml":
if buf, err = c.jsonToYaml(defaultConfig()); err != nil {
return err
}
default:
buf = defaultConfig()
cfgFile = strings.TrimRight(cfgFile, ext) + ".json"
}
fs, err = os.OpenFile(cfgFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return err
} else {
defer func() {
if fs != nil {
_ = fs.Close()
}
}()
}
_, err = fs.Write(buf)
if err != nil {
if nbr, err = io.Copy(fs, buf); err != nil {
return err
} else if nbr < 1 {
return fmt.Errorf("error wrting 0 byte to config file")
} else if err = fs.Close(); err != nil {
fs = nil
return err
} else {
fs = nil
}
err = os.Chmod(cfgFile, 0600)
@@ -140,6 +143,46 @@ func (c *cobra) ConfigureWriteConfig(basename string, defaultConfig func() io.Re
return nil
}
func (c *cobra) jsonToToml(r io.Reader) (io.Reader, error) {
var (
e error
p = make([]byte, 0)
buf = bytes.NewBuffer(p)
mod = make(map[string]interface{}, 0)
)
if p, e = io.ReadAll(r); e != nil {
return nil, e
} else if e = json.Unmarshal(p, &mod); e != nil {
return nil, e
} else if p, e = toml.Marshal(mod); e != nil {
return nil, e
} else {
buf.Write(p)
return buf, nil
}
}
func (c *cobra) jsonToYaml(r io.Reader) (io.Reader, error) {
var (
e error
p = make([]byte, 0)
buf = bytes.NewBuffer(p)
mod = make(map[string]interface{}, 0)
)
if p, e = io.ReadAll(r); e != nil {
return nil, e
} else if e = json.Unmarshal(p, &mod); e != nil {
return nil, e
} else if p, e = yaml.Marshal(mod); e != nil {
return nil, e
} else {
buf.Write(p)
return buf, nil
}
}
func (c *cobra) AddCommandConfigure(basename string, defaultConfig func() io.Reader) {
pkg := c.getPackageName()

View File

@@ -175,6 +175,14 @@ type Error interface {
ReturnParent(f ReturnError)
}
type Errors interface {
// ErrorsLast return the last registered error
ErrorsLast() error
// ErrorsList return a slice of all registered errors
ErrorsList() []error
}
func MakeErrorIfError(err ...Error) Error {
var e Error = nil

26
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
github.com/aws/aws-sdk-go-v2/service/iam v1.19.8
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1
github.com/bits-and-blooms/bitset v1.5.0
github.com/c-bata/go-prompt v0.2.6
github.com/fatih/color v1.15.0
@@ -37,19 +37,19 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.7.0
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/viper v1.15.0
github.com/vbauerster/mpb/v5 v5.4.0
github.com/xanzy/go-gitlab v0.81.0
github.com/xanzy/go-gitlab v0.82.0
github.com/xhit/go-simple-mail v2.2.2+incompatible
github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.6.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.6.0
golang.org/x/term v0.6.0
golang.org/x/sys v0.7.0
golang.org/x/term v0.7.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/clickhouse v0.5.0
gorm.io/driver/mysql v1.4.7
@@ -62,7 +62,7 @@ require (
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/ClickHouse/ch-go v0.54.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.8.1 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.8.3 // indirect
github.com/DataDog/zstd v1.5.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
@@ -90,14 +90,14 @@ require (
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/bytedance/sonic v1.8.6 // indirect
github.com/bytedance/sonic v1.8.7 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20210331181633-27fc006b8bfb // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
github.com/getsentry/sentry-go v0.19.0 // indirect
github.com/getsentry/sentry-go v0.20.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-faster/city v1.0.1 // indirect
@@ -117,7 +117,7 @@ require (
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -140,11 +140,11 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/klauspost/compress v1.16.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.2 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/lni/goutils v1.3.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
@@ -194,8 +194,8 @@ require (
go.opentelemetry.io/otel/trace v1.14.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect

View File

@@ -32,7 +32,6 @@ import (
liblog "github.com/nabbar/golib/logger"
libctx "github.com/nabbar/golib/context"
srvrun "github.com/nabbar/golib/httpserver/run"
srvtps "github.com/nabbar/golib/httpserver/types"
montps "github.com/nabbar/golib/monitor/types"
libsrv "github.com/nabbar/golib/server"
@@ -50,7 +49,6 @@ type Info interface {
type Server interface {
libsrv.Server
libsrv.WaitNotify
Info
@@ -74,8 +72,6 @@ func New(cfg Config, defLog liblog.FuncLog) (Server, error) {
if e := s.SetConfig(cfg, defLog); e != nil {
return nil, e
} else {
s.r = srvrun.New()
}
return s, nil

View File

@@ -27,12 +27,13 @@
package httpserver
import (
"net/http"
"sync"
libctx "github.com/nabbar/golib/context"
srvrun "github.com/nabbar/golib/httpserver/run"
srvtps "github.com/nabbar/golib/httpserver/types"
liblog "github.com/nabbar/golib/logger"
librun "github.com/nabbar/golib/server/runner/startStop"
)
type srv struct {
@@ -40,7 +41,8 @@ type srv struct {
h srvtps.FuncHandler
l liblog.FuncLog
c libctx.Config[string]
r srvrun.Run
r librun.StartStop
s *http.Server
}
func (o *srv) Merge(s Server, def liblog.FuncLog) error {

View File

@@ -28,6 +28,7 @@ package httpserver
import (
"context"
"errors"
"fmt"
"runtime"
@@ -41,21 +42,38 @@ const (
defaultNameMonitor = "HTTP Server"
)
var (
errNotRunning = errors.New("server is not running")
)
func (o *srv) HealthCheck(ctx context.Context) error {
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return fmt.Errorf("server is not running")
} else if o.r.IsRunning() {
return nil
} else if e := o.r.GetError(); e != nil {
return errNotRunning
} else if e := o.runAndHealthy(ctx); e != nil {
return e
} else if e = o.r.ErrorsLast(); e != nil {
return e
} else {
return fmt.Errorf("server is not running")
return errNotRunning
}
}
func (o *srv) runAndHealthy(ctx context.Context) error {
o.m.RLock()
defer o.m.RUnlock()
if !o.r.IsRunning() {
return errNotRunning
} else if e := o.PortNotUse(ctx, o.GetBindable()); e != nil {
return e
}
return nil
}
func (o *srv) Monitor(vrs libver.Version) (montps.Monitor, error) {
var (
e error

View File

@@ -65,7 +65,6 @@ type Filter interface {
type Pool interface {
libsrv.Server
libsrv.WaitNotify
Manage
Filter

229
httpserver/run.go Normal file
View File

@@ -0,0 +1,229 @@
/*
* MIT License
*
* Copyright (c) 2022 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"
"fmt"
"net"
"net/http"
srvtps "github.com/nabbar/golib/httpserver/types"
liblog "github.com/nabbar/golib/logger"
librun "github.com/nabbar/golib/server/runner/startStop"
)
func (o *srv) newRun(ctx context.Context) error {
if o == nil {
return ErrorServerValidate.Error(nil)
}
o.m.Lock()
defer o.m.Unlock()
if o.r != nil {
if e := o.r.Stop(ctx); e != nil {
return e
}
}
o.r = librun.New(o.runFuncStart, o.runFuncStop)
return nil
}
func (o *srv) delRun(ctx context.Context) error {
if o == nil {
return ErrorServerValidate.Error(nil)
}
o.m.Lock()
defer o.m.Unlock()
if o.r != nil {
if e := o.r.Stop(ctx); e != nil {
return e
}
}
o.r = nil
return nil
}
func (o *srv) runStart(ctx context.Context) error {
if o == nil {
return ErrorServerValidate.Error(nil)
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return ErrorServerValidate.Error(nil)
}
return o.r.Start(ctx)
}
func (o *srv) runStop(ctx context.Context) error {
if o == nil {
return ErrorServerValidate.Error(nil)
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return ErrorServerValidate.Error(nil)
}
return o.r.Stop(ctx)
}
func (o *srv) runRestart(ctx context.Context) error {
if o == nil {
return ErrorServerValidate.Error(nil)
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return ErrorServerValidate.Error(nil)
}
return o.r.Restart(ctx)
}
func (o *srv) runIsRunning() bool {
if o == nil {
return false
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return false
}
return o.r.IsRunning()
}
func (o *srv) runFuncStart(ctx context.Context) (err error) {
var (
tls = false
ser *http.Server
)
defer func() {
if tls {
ent := o.logger().Entry(liblog.InfoLevel, "TLS HTTP Server stopped")
ent.ErrorAdd(true, err)
ent.Log()
} else {
ent := o.logger().Entry(liblog.InfoLevel, "HTTP Server stopped")
ent.ErrorAdd(true, err)
ent.Log()
}
}()
if ser = o.getServer(); ser == nil {
if err = o.setServer(ctx); err != nil {
ent := o.logger().Entry(liblog.ErrorLevel, "starting http server")
ent.ErrorAdd(true, err)
ent.Log()
return err
} else if ser = o.getServer(); ser == nil {
err = ErrorServerStart.ErrorParent(fmt.Errorf("cannot create new server, cannot retrieve server"))
ent := o.logger().Entry(liblog.ErrorLevel, "starting http server")
ent.ErrorAdd(true, err)
ent.Log()
return err
}
}
if ser.TLSConfig != nil && len(ser.TLSConfig.Certificates) > 0 {
tls = true
}
ser.BaseContext = func(listener net.Listener) context.Context {
return ctx
}
if tls {
o.logger().Entry(liblog.InfoLevel, "TLS HTTP Server is starting").Log()
err = ser.ListenAndServeTLS("", "")
} else {
o.logger().Entry(liblog.InfoLevel, "HTTP Server is starting").Log()
err = ser.ListenAndServe()
}
return err
}
func (o *srv) runFuncStop(ctx context.Context) (err error) {
var x, n = context.WithTimeout(ctx, srvtps.TimeoutWaitingStop)
defer n()
var (
tls = false
ser *http.Server
)
defer func() {
o.delServer()
if tls {
ent := o.logger().Entry(liblog.InfoLevel, "Shutdown of TLS HTTP Server has been called")
ent.ErrorAdd(true, err)
ent.Log()
} else {
ent := o.logger().Entry(liblog.InfoLevel, "Shutdown of HTTP Server has been called")
ent.ErrorAdd(true, err)
ent.Log()
}
}()
if ser = o.getServer(); ser == nil {
err = ErrorServerStart.ErrorParent(fmt.Errorf("cannot retrieve server"))
ent := o.logger().Entry(liblog.ErrorLevel, "starting http server")
ent.ErrorAdd(true, err)
ent.Log()
return err
} else if ser.TLSConfig != nil && len(ser.TLSConfig.Certificates) > 0 {
tls = true
}
if tls {
o.logger().Entry(liblog.InfoLevel, "Calling TLS HTTP Server shutdown").Log()
} else {
o.logger().Entry(liblog.InfoLevel, "Calling HTTP Server shutdown").Log()
}
err = ser.Shutdown(x)
return err
}

View File

@@ -1,119 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 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 run
import (
"context"
"net"
"net/http"
"sync"
liblog "github.com/nabbar/golib/logger"
)
type sRun struct {
m sync.RWMutex
err error
chn chan struct{}
ctx context.Context
cnl context.CancelFunc
log liblog.FuncLog
srv *http.Server
run bool
tls bool
}
func (o *sRun) logger() liblog.Logger {
o.m.RLock()
defer o.m.RUnlock()
if o.log == nil {
return liblog.GetDefault()
} else if l := o.log(); l == nil {
return liblog.GetDefault()
} else {
return l
}
}
func (o *sRun) SetServer(srv *http.Server, log liblog.FuncLog, tls bool) {
o.m.Lock()
defer o.m.Unlock()
srv.BaseContext = func(listener net.Listener) context.Context {
return o.getContext()
}
o.srv = srv
o.log = log
o.tls = tls
}
func (o *sRun) getServer() *http.Server {
o.m.RLock()
defer o.m.RUnlock()
return o.srv
}
func (o *sRun) delServer() {
o.m.Lock()
defer o.m.Unlock()
if o.srv != nil {
o.logger().Entry(liblog.ErrorLevel, "closing server").ErrorAdd(true, o.srv.Close()).Check(liblog.NilLevel)
o.srv = nil
}
}
func (o *sRun) getContext() context.Context {
o.m.RLock()
defer o.m.RUnlock()
return o.ctx
}
func (o *sRun) getCancel() context.CancelFunc {
o.m.RLock()
defer o.m.RUnlock()
return o.cnl
}
func (o *sRun) isTLS() bool {
o.m.RLock()
defer o.m.RUnlock()
return o.tls
}
func (o *sRun) GetError() error {
o.m.RLock()
defer o.m.RUnlock()
return o.err
}
func (o *sRun) setError(err error) {
o.m.Lock()
defer o.m.Unlock()
o.err = err
}

View File

@@ -1,136 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022 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 run
import (
"context"
"errors"
"fmt"
"net/http"
srvtps "github.com/nabbar/golib/httpserver/types"
liblog "github.com/nabbar/golib/logger"
)
func (o *sRun) Start(ctx context.Context) error {
o.m.Lock()
defer o.m.Unlock()
if o == nil || o.srv == nil {
return fmt.Errorf("invalid instance")
}
o.ctx, o.cnl = context.WithCancel(ctx)
go o.runServer()
return nil
}
func (o *sRun) runServer() {
defer func() {
cnl := o.getCancel()
if cnl != nil {
cnl()
}
o.setRunning(false)
o.logger().Entry(liblog.InfoLevel, "server stopped").Log()
}()
var err error
o.logger().Entry(liblog.InfoLevel, "Server is starting").Log()
o.setError(nil)
o.setRunning(true)
if o.isTLS() {
err = o.getServer().ListenAndServeTLS("", "")
} else {
err = o.getServer().ListenAndServe()
}
if err != nil {
x := o.getContext()
if x != nil && x.Err() != nil && errors.Is(err, x.Err()) {
err = nil
} else if errors.Is(err, http.ErrServerClosed) {
err = nil
}
}
o.setError(err)
o.logger().Entry(liblog.InfoLevel, "Server return an error").ErrorAdd(true, err).Check(liblog.NilLevel)
}
func (o *sRun) Stop(ctx context.Context) error {
var cnl context.CancelFunc
if ctx.Err() != nil {
ctx = context.Background()
}
if t, ok := ctx.Deadline(); !ok || t.IsZero() {
ctx, cnl = context.WithTimeout(ctx, srvtps.TimeoutWaitingStop)
}
defer func() {
if cnl != nil {
cnl()
}
//o.delServer()
}()
var err error
if o.IsRunning() {
o.StopWaitNotify()
if srv := o.getServer(); srv != nil {
err = srv.Shutdown(ctx)
}
}
if err != nil && !errors.Is(err, http.ErrServerClosed) {
err = nil
}
o.logger().Entry(liblog.ErrorLevel, "Shutting down server").ErrorAdd(true, err).Check(liblog.NilLevel)
return err
}
func (o *sRun) Restart(ctx context.Context) error {
_ = o.Stop(ctx)
return o.Start(ctx)
}
func (o *sRun) IsRunning() bool {
o.m.RLock()
defer o.m.RUnlock()
return o.run
}
func (o *sRun) setRunning(flag bool) {
o.m.Lock()
defer o.m.Unlock()
o.run = flag
}

View File

@@ -28,109 +28,126 @@ package httpserver
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"time"
liberr "github.com/nabbar/golib/errors"
srvtps "github.com/nabbar/golib/httpserver/types"
liblog "github.com/nabbar/golib/logger"
libsrv "github.com/nabbar/golib/server"
)
func (o *srv) Start(ctx context.Context) error {
ssl := o.cfgGetTLS()
var errInvalid = errors.New("invalid instance")
if o.IsTLS() && ssl == nil {
return ErrorServerValidate.ErrorParent(fmt.Errorf("TLS Config is not well defined"))
func (o *srv) getServer() *http.Server {
if o == nil {
return nil
}
bind := o.GetBindable()
name := o.GetName()
if name == "" {
o.m.RLock()
defer o.m.RUnlock()
return o.s
}
func (o *srv) delServer() {
if o == nil {
return
}
o.m.Lock()
defer o.m.Unlock()
o.s = nil
}
func (o *srv) setServer(ctx context.Context) error {
if o == nil {
return errInvalid
}
var (
ssl = o.cfgGetTLS()
bind = o.GetBindable()
name = o.GetName()
fctStop = func() {
_ = o.Stop(ctx)
}
)
if o.IsTLS() && ssl == nil {
err := ErrorServerValidate.ErrorParent(fmt.Errorf("TLS Config is not well defined"))
ent := o.logger().Entry(liblog.ErrorLevel, "starting http server")
ent.ErrorAdd(true, err)
ent.Log()
return err
} else if name == "" {
name = bind
}
var stdlog = o.logger()
s := &http.Server{
Addr: bind,
ErrorLog: o.logger().GetStdLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds),
Handler: o.HandlerLoadFct(),
Addr: bind,
Handler: o.HandlerLoadFct(),
}
if ssl != nil && ssl.LenCertificatePair() > 0 {
s.TLSConfig = ssl.TlsConfig("")
}
if e := o.cfgGetServer().initServer(s); e != nil {
stdlog.SetIOWriterFilter("http: TLS handshake error from 127.0.0.1")
} else if e := o.cfgGetServer().initServer(s); e != nil {
ent := o.logger().Entry(liblog.ErrorLevel, "init http2 server")
ent.ErrorAdd(true, e)
ent.Log()
return e
}
if o.IsRunning() {
_ = o.Stop(ctx)
}
s.ErrorLog = stdlog.GetStdLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds)
for i := 0; i < 5; i++ {
if e := o.PortInUse(o.GetBindable()); e != nil {
_ = o.Stop(ctx)
} else {
break
}
}
o.m.RLock()
defer o.m.RUnlock()
o.r.SetServer(s, o.logger, ssl != nil)
if e := o.r.Start(ctx); e != nil {
if e := o.RunIfPortInUse(ctx, o.GetBindable(), 5, fctStop); e != nil {
return e
}
for i := 0; i < 5; i++ {
ok := false
ts := time.Now()
o.m.Lock()
o.s = s
o.m.Unlock()
for {
time.Sleep(100 * time.Millisecond)
ok = o.r.IsRunning()
return nil
}
if ok {
break
} else if time.Since(ts) > 10*time.Second {
break
}
}
if !ok {
_ = o.r.Restart(ctx)
} else {
ts = time.Now()
for {
time.Sleep(100 * time.Millisecond)
if e := o.PortInUse(o.GetBindable()); e != nil {
return nil
} else if time.Since(ts) > 10*time.Second {
break
}
}
_ = o.r.Restart(ctx)
func (o *srv) Start(ctx context.Context) error {
// Register Server to runner
if o.getServer() != nil {
if e := o.Stop(ctx); e != nil {
return e
}
}
_ = o.r.Stop(ctx)
return ErrorServerStart.Error(nil)
if e := o.newRun(ctx); e != nil {
return e
} else if e = o.runStart(ctx); e != nil {
return e
}
return nil
}
func (o *srv) Stop(ctx context.Context) error {
if o == nil {
return errInvalid
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return nil
}
return o.r.Stop(ctx)
}
@@ -140,17 +157,25 @@ func (o *srv) Restart(ctx context.Context) error {
}
func (o *srv) IsRunning() bool {
if o == nil {
return false
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return false
}
return o.r.IsRunning()
}
func (o *srv) PortInUse(listen string) liberr.Error {
func (o *srv) PortInUse(ctx context.Context, listen string) liberr.Error {
var (
dia = net.Dialer{}
con net.Conn
err error
ctx context.Context
cnl context.CancelFunc
)
@@ -163,7 +188,7 @@ func (o *srv) PortInUse(listen string) liberr.Error {
}
}()
ctx, cnl = context.WithTimeout(context.TODO(), srvtps.TimeoutWaitingPortFreeing)
ctx, cnl = context.WithTimeout(ctx, srvtps.TimeoutWaitingPortFreeing)
con, err = dia.DialContext(ctx, "tcp", listen)
if con != nil {
@@ -180,3 +205,37 @@ func (o *srv) PortInUse(listen string) liberr.Error {
return ErrorPortUse.Error(nil)
}
func (o *srv) PortNotUse(ctx context.Context, listen string) error {
var (
err error
cnl context.CancelFunc
con net.Conn
dia = net.Dialer{}
)
ctx, cnl = context.WithTimeout(context.TODO(), srvtps.TimeoutWaitingPortFreeing)
defer cnl()
con, err = dia.DialContext(ctx, "tcp", listen)
defer func() {
if con != nil {
_ = con.Close()
}
}()
return err
}
func (o *srv) RunIfPortInUse(ctx context.Context, listen string, nbr uint8, fct func()) liberr.Error {
chk := func() bool {
return o.PortInUse(ctx, listen) == nil
}
if !libsrv.RunNbr(nbr, chk, fct) {
return ErrorPortUse.Error(nil)
}
return nil
}

View File

@@ -29,7 +29,7 @@ package types
import "time"
const (
TimeoutWaitingPortFreeing = 500 * time.Microsecond
TimeoutWaitingStop = 10 * time.Second
TimeoutWaitingPortFreeing = 100 * time.Microsecond
TimeoutWaitingStop = 5 * time.Second
BadHandlerName = "no handler"
)

View File

@@ -59,6 +59,9 @@ type Logger interface {
//GetIOWriterLevel return the minimal level of log message for io.WriterCloser interface
GetIOWriterLevel() Level
// SetIOWriterFilter allow to filter message that contained the given pattern. If the pattern is found, the log is drop.
SetIOWriterFilter(pattern string)
//SetOptions allow to set or update the options for the logger
SetOptions(opt *Options) error

View File

@@ -27,7 +27,11 @@
package logger
import "fmt"
import (
"bytes"
"fmt"
"strings"
)
func (o *logger) Close() error {
if o == nil {
@@ -49,7 +53,13 @@ func (o *logger) Write(p []byte) (n int, err error) {
return
}
o.newEntry(o.GetIOWriterLevel(), string(p), nil, o.GetFields(), nil).Log()
val := strings.TrimSpace(string(o.IOWriterFilter(p)))
if len(val) < 1 {
return len(p), nil
}
o.newEntry(o.GetIOWriterLevel(), val, nil, o.GetFields(), nil).Log()
return len(p), nil
}
@@ -62,7 +72,6 @@ func (o *logger) SetIOWriterLevel(lvl Level) {
o.x.Store(keyWriter, lvl)
}
func (o *logger) GetIOWriterLevel() Level {
if o == nil {
return NilLevel
@@ -76,3 +85,29 @@ func (o *logger) GetIOWriterLevel() Level {
return v
}
}
func (o *logger) SetIOWriterFilter(pattern string) {
if o == nil {
return
} else if o.x == nil {
return
}
o.x.Store(keyFilter, []byte(pattern))
}
func (o *logger) IOWriterFilter(p []byte) []byte {
if o == nil {
return p
} else if o.x == nil {
return p
} else if i, l := o.x.Load(keyFilter); !l {
return p
} else if v, k := i.([]byte); !k {
return p
} else if bytes.Contains(p, v) {
return make([]byte, 0)
} else {
return p
}
}

View File

@@ -51,6 +51,7 @@ const (
keyOptions
keyLogrus
keyWriter
keyFilter
_TraceFilterMod = "/pkg/mod/"
_TraceFilterVendor = "/vendor/"

View File

@@ -38,6 +38,7 @@ const (
ErrorValidatorError
ErrorLoggerError
ErrorTimeout
ErrorInvalid
)
func init() {
@@ -59,6 +60,8 @@ func getMessage(code liberr.CodeError) (message string) {
return "cannot initialize logger"
case ErrorTimeout:
return "timeout error"
case ErrorInvalid:
return "invalid instance"
}
return liberr.NullMessage

View File

@@ -50,6 +50,6 @@ func New(ctx libctx.FuncContext, info montps.Info) (montps.Monitor, error) {
m: sync.RWMutex{},
i: info,
x: libctx.NewConfig[string](ctx),
s: make(chan struct{}),
r: nil,
}, nil
}

View File

@@ -33,6 +33,7 @@ import (
libctx "github.com/nabbar/golib/context"
liberr "github.com/nabbar/golib/errors"
montps "github.com/nabbar/golib/monitor/types"
librun "github.com/nabbar/golib/server/runner/ticker"
)
const (
@@ -64,7 +65,7 @@ type mon struct {
m sync.RWMutex
i montps.Info
x libctx.Config[string]
s chan struct{}
r librun.Ticker
}
func (o *mon) SetHealthCheck(fct montps.HealthCheck) {

View File

@@ -30,71 +30,58 @@ import (
"context"
"time"
"github.com/nabbar/golib/monitor/types"
montps "github.com/nabbar/golib/monitor/types"
librun "github.com/nabbar/golib/server/runner/ticker"
)
func (o *mon) setChan() {
o.m.Lock()
defer o.m.Unlock()
o.s = make(chan struct{})
}
func (o *mon) getChan() <-chan struct{} {
o.m.RLock()
defer o.m.RUnlock()
return o.s
}
func (o *mon) sendChan() {
o.m.RLock()
defer o.m.RUnlock()
o.s <- struct{}{}
}
const (
MaxPoolStart = 15 * time.Second
MaxTickPooler = 500 * time.Millisecond
)
func (o *mon) Start(ctx context.Context) error {
if o.IsRunning() {
_ = o.Stop(ctx)
}
o.setChan()
go o.ticker(ctx)
if o.IsRunning() {
return nil
if o == nil {
return ErrorInvalid.Error(nil)
}
t := time.Now()
for {
if time.Since(t) > 15*time.Second {
return ErrorTimeout.Error(nil)
} else if o.IsRunning() {
return nil
}
o.setRunner(ctx)
time.Sleep(100 * time.Millisecond)
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return ErrorInvalid.Error(nil)
}
if e := o.r.Start(ctx); e != nil {
return e
} else {
return o.poolIsRunning(ctx)
}
}
func (o *mon) Stop(ctx context.Context) error {
if !o.IsRunning() {
defer o.delRunner(ctx)
if o == nil {
return ErrorInvalid.Error(nil)
} else if !o.IsRunning() {
return nil
}
t := time.Now()
o.m.RLock()
defer o.m.RUnlock()
for {
o.sendChan()
time.Sleep(100 * time.Millisecond)
if time.Since(t) > 15*time.Second {
return ErrorTimeout.Error(nil)
} else if !o.IsRunning() {
return nil
}
if o.r == nil {
return ErrorInvalid.Error(nil)
} else if e := o.r.Stop(ctx); e != nil {
return e
}
return nil
}
func (o *mon) Restart(ctx context.Context) error {
@@ -108,63 +95,104 @@ func (o *mon) Restart(ctx context.Context) error {
}
func (o *mon) IsRunning() bool {
if i, l := o.x.Load(keyRun); !l {
if o == nil {
return false
} else if v, k := i.(bool); !k {
}
o.m.RLock()
defer o.m.RUnlock()
if o.r == nil {
return false
} else {
return v
return o.r.IsRunning()
}
}
func (o *mon) setRunning(state bool) {
if state {
o.x.Store(keyRun, state)
} else {
o.x.Delete(keyRun)
func (o *mon) poolIsRunning(ctx context.Context) error {
if o == nil {
return ErrorInvalid.Error(nil)
} else if o.IsRunning() {
return nil
}
}
func (o *mon) ticker(ctx context.Context) {
var (
cfg = o.getCfg()
chg = false
tck *time.Ticker
tck = time.NewTicker(MaxTickPooler)
tms = time.Now()
)
tck = time.NewTicker(cfg.intervalCheck)
defer tck.Stop()
o.setRunning(true)
defer o.setRunning(false)
for {
select {
case <-tck.C:
o.check(ctx, cfg)
if o.IsRise() {
tck.Reset(cfg.intervalRise)
chg = true
} else if o.IsFall() {
tck.Reset(cfg.intervalFall)
chg = true
} else if chg {
tck.Reset(cfg.intervalCheck)
chg = false
if time.Since(tms) >= MaxPoolStart {
return ErrorTimeout.Error(nil)
} else if o.IsRunning() {
return nil
}
case <-ctx.Done():
return
case <-o.getChan():
return
return ctx.Err()
}
}
}
func (o *mon) setRunner(ctx context.Context) {
if o == nil {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.r != nil {
_ = o.r.Stop(ctx)
}
var (
cfg = o.getCfg()
)
o.r = librun.New(cfg.intervalCheck, o.runFunc)
}
func (o *mon) delRunner(ctx context.Context) {
if o == nil {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.r != nil {
_ = o.r.Stop(ctx)
}
o.r = nil
}
func (o *mon) runFunc(ctx context.Context, tck *time.Ticker) error {
var (
cfg = o.getCfg()
chg = false
)
o.check(ctx, cfg)
if o.IsRise() {
tck.Reset(cfg.intervalRise)
chg = true
} else if o.IsFall() {
tck.Reset(cfg.intervalFall)
chg = true
} else if chg {
tck.Reset(cfg.intervalCheck)
chg = false
}
return nil
}
func (o *mon) check(ctx context.Context, cfg *runCfg) {
var fct types.HealthCheck
var fct montps.HealthCheck
if fct = o.getFct(); fct == nil {
l := o.getLastCheck()

View File

@@ -115,6 +115,7 @@ type Request interface {
Do() (*http.Response, liberr.Error)
DoParse(model interface{}, validStatus ...int) liberr.Error
DoParseRetry(retry int, model interface{}, validStatus ...int) liberr.Error
Monitor(ctx context.Context, vrs libver.Version) (montps.Monitor, error)
HealthCheck(ctx context.Context) error

View File

@@ -213,3 +213,19 @@ func (r *request) DoParse(model interface{}, validStatus ...int) liberr.Error {
return nil
}
func (r *request) DoParseRetry(retry int, model interface{}, validStatus ...int) liberr.Error {
var e liberr.Error
for i := 0; i < retry; i++ {
if e = r.DoParse(model, validStatus...); e != nil {
continue
} else if r.IsError() {
continue
} else {
return nil
}
}
return e
}

View File

@@ -26,7 +26,11 @@
package server
import "context"
import (
"context"
)
type Action func(ctx context.Context) error
type Server interface {
// Start is used to start the server

View File

@@ -24,71 +24,78 @@
*
*/
package run
package startStop
import (
"context"
"os"
"os/signal"
"syscall"
)
var closeChan = make(chan struct{})
func (o *sRun) StartWaitNotify(ctx context.Context) {
if !o.IsRunning() {
func init() {
close(closeChan)
}
func (o *run) IsRunning() bool {
if e := o.checkMe(); e != nil {
return false
}
o.m.RLock()
defer o.m.RUnlock()
if o.c == nil || o.c == closeChan {
return false
}
select {
case <-o.c:
return false
default:
return true
}
}
func (o *run) chanInit() {
if e := o.checkMe(); e != nil {
return
}
// 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)
o.initChan()
defer o.delChan()
for {
select {
case <-quit:
_ = o.Stop(ctx)
return
case <-o.getContext().Done():
if o.IsRunning() {
_ = o.Stop(ctx)
}
return
case <-o.getChan():
return
}
}
}
func (o *sRun) StopWaitNotify() {
o.m.RLock()
defer o.m.RUnlock()
if o.chn != nil {
o.chn <- struct{}{}
}
}
func (o *sRun) initChan() {
o.m.Lock()
defer o.m.Unlock()
o.chn = make(chan struct{})
o.c = make(chan struct{})
}
func (o *sRun) getChan() <-chan struct{} {
func (o *run) chanDone() <-chan struct{} {
if e := o.checkMe(); e != nil {
return nil
}
o.m.RLock()
defer o.m.RUnlock()
return o.chn
return o.c
}
func (o *sRun) delChan() {
func (o *run) chanSend() {
if e := o.checkMe(); e != nil {
return
}
o.m.RLock()
defer o.m.RUnlock()
if o.c != nil && o.c != closeChan {
o.c <- struct{}{}
}
}
func (o *run) chanClose() {
if e := o.checkMe(); e != nil {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.chn != nil {
close(o.chn)
if o.c != nil && o.c != closeChan {
o.c = closeChan
}
o.chn = nil
}

View File

@@ -0,0 +1,82 @@
/*
* MIT License
*
* Copyright (c) 2022 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 startStop
func (o *run) ErrorsLast() error {
if o == nil {
return ErrInvalid
}
o.m.RLock()
defer o.m.RUnlock()
if len(o.e) > 0 {
return o.e[len(o.e)-1]
}
return nil
}
func (o *run) ErrorsList() []error {
var res = make([]error, 0)
if o == nil {
res = append(res, ErrInvalid)
return res
}
o.m.RLock()
defer o.m.RUnlock()
if len(o.e) > 0 {
return o.e
}
return res
}
func (o *run) errorsAdd(e error) {
if o == nil {
return
}
o.m.RLock()
defer o.m.RUnlock()
o.e = append(o.e, e)
}
func (o *run) errorsClean() {
if o == nil {
return
}
o.m.Lock()
defer o.m.Unlock()
o.e = make([]error, 0)
}

View File

@@ -24,30 +24,27 @@
*
*/
package run
package startStop
import (
"net/http"
"context"
"sync"
liblog "github.com/nabbar/golib/logger"
liberr "github.com/nabbar/golib/errors"
libsrv "github.com/nabbar/golib/server"
)
type Run interface {
type StartStop interface {
libsrv.Server
libsrv.WaitNotify
GetError() error
SetServer(srv *http.Server, log liblog.FuncLog, tls bool)
liberr.Errors
}
func New() Run {
return &sRun{
m: sync.RWMutex{},
ctx: nil,
cnl: nil,
log: nil,
srv: nil,
func New(start, stop func(ctx context.Context) error) StartStop {
return &run{
m: sync.RWMutex{},
e: make([]error, 0),
f: start,
s: stop,
c: nil,
}
}

View File

@@ -0,0 +1,168 @@
/*
* MIT License
*
* Copyright (c) 2022 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 startStop
import (
"context"
"errors"
"sync"
"time"
libsrv "github.com/nabbar/golib/server"
)
const (
pollStop = 500 * time.Millisecond
)
var ErrInvalid = errors.New("invalid instance")
type run struct {
m sync.RWMutex
e []error
f func(ctx context.Context) error
s func(ctx context.Context) error
c chan struct{}
}
func (o *run) Restart(ctx context.Context) error {
if e := o.Stop(ctx); e != nil {
return e
} else if e = o.Start(ctx); e != nil {
_ = o.Stop(ctx)
return e
}
return nil
}
func (o *run) Stop(ctx context.Context) error {
if e := o.checkMe(); e != nil {
return e
} else if !o.IsRunning() {
return nil
}
var (
e error
t = time.NewTicker(pollStop)
)
defer t.Stop()
for {
select {
case <-t.C:
if o.IsRunning() {
o.chanSend()
e = o.callStop(ctx)
} else {
return e
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func (o *run) callStop(ctx context.Context) error {
o.m.RLock()
defer o.m.RUnlock()
if o.s == nil {
return nil
}
return o.s(ctx)
}
func (o *run) Start(ctx context.Context) error {
if e := o.checkMeStart(ctx); e != nil {
return e
}
o.m.RLock()
defer o.m.RUnlock()
var can context.CancelFunc
ctx, can = context.WithCancel(ctx)
go func(x context.Context, n context.CancelFunc, fct libsrv.Action) {
defer n()
o.chanInit()
defer o.chanClose()
if e := fct(x); e != nil {
o.errorsAdd(e)
return
}
}(ctx, can, o.f)
go func(x context.Context, n context.CancelFunc) {
defer n()
defer o.chanClose()
for {
select {
case <-o.chanDone():
return
case <-x.Done():
return
}
}
}(ctx, can)
return nil
}
func (o *run) checkMe() error {
if o == nil {
return ErrInvalid
}
o.m.RLock()
defer o.m.RUnlock()
if o.f == nil || o.s == nil {
return ErrInvalid
}
return nil
}
func (o *run) checkMeStart(ctx context.Context) error {
if e := o.checkMe(); e != nil {
return e
} else if o.IsRunning() {
if e = o.Stop(ctx); e != nil {
return e
}
}
return nil
}

View File

@@ -0,0 +1,101 @@
/*
* MIT License
*
* Copyright (c) 2022 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 ticker
var closeChan = make(chan struct{})
func init() {
close(closeChan)
}
func (o *run) IsRunning() bool {
if e := o.checkMe(); e != nil {
return false
}
o.m.RLock()
defer o.m.RUnlock()
if o.c == nil || o.c == closeChan {
return false
}
select {
case <-o.c:
return false
default:
return true
}
}
func (o *run) chanInit() {
if e := o.checkMe(); e != nil {
return
}
o.m.Lock()
defer o.m.Unlock()
o.c = make(chan struct{})
}
func (o *run) chanDone() <-chan struct{} {
if e := o.checkMe(); e != nil {
return nil
}
o.m.RLock()
defer o.m.RUnlock()
return o.c
}
func (o *run) chanSend() {
if e := o.checkMe(); e != nil {
return
}
o.m.RLock()
defer o.m.RUnlock()
if o.c != nil && o.c != closeChan {
o.c <- struct{}{}
}
}
func (o *run) chanClose() {
if e := o.checkMe(); e != nil {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.c != nil && o.c != closeChan {
o.c = closeChan
}
}

View File

@@ -24,18 +24,59 @@
*
*/
package httpserver
package ticker
import "context"
func (o *run) ErrorsLast() error {
if o == nil {
return ErrInvalid
}
func (o *srv) StartWaitNotify(ctx context.Context) {
o.m.RLock()
defer o.m.RUnlock()
go o.r.StartWaitNotify(ctx)
if len(o.e) > 0 {
return o.e[len(o.e)-1]
}
return nil
}
func (o *srv) StopWaitNotify() {
func (o *run) ErrorsList() []error {
var res = make([]error, 0)
if o == nil {
res = append(res, ErrInvalid)
return res
}
o.m.RLock()
defer o.m.RUnlock()
go o.r.StopWaitNotify()
if len(o.e) > 0 {
return o.e
}
return res
}
func (o *run) errorsAdd(e error) {
if o == nil {
return
}
o.m.RLock()
defer o.m.RUnlock()
o.e = append(o.e, e)
}
func (o *run) errorsClean() {
if o == nil {
return
}
o.m.Lock()
defer o.m.Unlock()
o.e = make([]error, 0)
}

View File

@@ -24,24 +24,28 @@
*
*/
package pool
package ticker
import (
"context"
"sync"
"time"
libhtp "github.com/nabbar/golib/httpserver"
liberr "github.com/nabbar/golib/errors"
libsrv "github.com/nabbar/golib/server"
)
func (o *pool) StartWaitNotify(ctx context.Context) {
o.Walk(func(bindAddress string, srv libhtp.Server) bool {
srv.StartWaitNotify(ctx)
return true
})
type Ticker interface {
libsrv.Server
liberr.Errors
}
func (o *pool) StopWaitNotify() {
o.Walk(func(bindAddress string, srv libhtp.Server) bool {
srv.StopWaitNotify()
return true
})
func New(tick time.Duration, fct func(ctx context.Context, tck *time.Ticker) error) Ticker {
return &run{
m: sync.RWMutex{},
e: make([]error, 0),
f: fct,
d: tick,
c: nil,
}
}

View File

@@ -0,0 +1,155 @@
/*
* MIT License
*
* Copyright (c) 2022 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 ticker
import (
"context"
"errors"
"sync"
"time"
)
const (
pollStop = 500 * time.Millisecond
)
var ErrInvalid = errors.New("invalid instance")
type run struct {
m sync.RWMutex
e []error
f func(ctx context.Context, tck *time.Ticker) error
d time.Duration
c chan struct{}
}
func (o *run) Restart(ctx context.Context) error {
if e := o.Stop(ctx); e != nil {
return e
} else if e = o.Start(ctx); e != nil {
_ = o.Stop(ctx)
return e
}
return nil
}
func (o *run) Stop(ctx context.Context) error {
if e := o.checkMe(); e != nil {
return e
} else if !o.IsRunning() {
return nil
}
var t = time.NewTicker(pollStop)
defer t.Stop()
for {
select {
case <-t.C:
if o.IsRunning() {
o.chanSend()
} else {
return nil
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func (o *run) Start(ctx context.Context) error {
if e := o.checkMeStart(ctx); e != nil {
return e
}
o.m.RLock()
defer o.m.RUnlock()
go func(con context.Context, dur time.Duration, fct func(ctx context.Context, tck *time.Ticker) error) {
var (
tck = time.NewTicker(dur)
x, n = context.WithCancel(con)
)
defer func() {
if n != nil {
n()
}
if tck != nil {
tck.Stop()
}
o.chanClose()
}()
o.chanInit()
o.errorsClean()
for {
select {
case <-tck.C:
if e := fct(x, tck); e != nil {
o.errorsAdd(e)
}
case <-con.Done():
return
case <-o.chanDone():
return
}
}
}(ctx, o.d, o.f)
return nil
}
func (o *run) checkMe() error {
if o == nil {
return ErrInvalid
}
o.m.RLock()
defer o.m.RUnlock()
if o.f == nil || o.d == 0 {
return ErrInvalid
}
return nil
}
func (o *run) checkMeStart(ctx context.Context) error {
if e := o.checkMe(); e != nil {
return e
} else if o.IsRunning() {
if e = o.Stop(ctx); e != nil {
return e
}
}
return nil
}

76
server/tools.go Normal file
View File

@@ -0,0 +1,76 @@
/*
* MIT License
*
* Copyright (c) 2022 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 server
import (
"context"
"time"
)
type FunCheck func() bool
type FunRun func()
func RunNbr(max uint8, chk FunCheck, run FunRun) bool {
var i uint8
for i = 0; i < max; i++ {
if chk() {
return true
}
run()
}
return chk()
}
func RunTick(ctx context.Context, tick, max time.Duration, chk FunCheck, run FunRun) bool {
var (
s = time.Now()
t = time.NewTicker(tick)
)
defer t.Stop()
for {
select {
case <-ctx.Done():
return false
case <-t.C:
if chk() {
return true
}
run()
if time.Since(s) >= max {
return chk()
}
}
}
}