-
Notifications
You must be signed in to change notification settings - Fork 41
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
add service lifecycle module #9
Changes from 5 commits
b16ac4d
a7f8aa9
316c3cb
7ead554
4ed160e
da5ce61
0e8f6e2
18fdf9b
773de0b
ed955db
7ec58bb
b1f42de
2a91b42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package lifecycle | ||
|
||
import "context" | ||
|
||
type Component interface { | ||
Name() string | ||
Init(ctx context.Context) error | ||
} | ||
|
||
type component struct { | ||
name string | ||
fn func(ctx context.Context) error | ||
} | ||
|
||
func NewComponent(name string, fn func(ctx context.Context) error) Component { | ||
return &component{ | ||
name: name, | ||
fn: fn, | ||
} | ||
} | ||
|
||
func (c *component) Name() string { | ||
return c.name | ||
} | ||
|
||
func (c *component) Init(ctx context.Context) error { | ||
return c.fn(ctx) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package lifecycle | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"os" | ||
"os/signal" | ||
"sync" | ||
"time" | ||
|
||
"github.com/bnb-chain/inscription-storage-provider/util/log" | ||
) | ||
|
||
type Service interface { | ||
Name() string | ||
Start(ctx context.Context) error | ||
Stop(ctx context.Context) error | ||
} | ||
|
||
type ServiceLifecycle struct { | ||
innerCtx context.Context | ||
innerCancel context.CancelFunc | ||
services []Service | ||
failure bool | ||
timeout time.Duration | ||
} | ||
|
||
// NewService returns an initialized service lifecycle | ||
func NewService(timeout time.Duration) *ServiceLifecycle { | ||
innerCtx, innerCancel := context.WithCancel(context.Background()) | ||
return &ServiceLifecycle{ | ||
innerCtx: innerCtx, | ||
innerCancel: innerCancel, | ||
timeout: timeout, | ||
} | ||
} | ||
|
||
// Init can be used to initialize components | ||
func (s *ServiceLifecycle) Init(ctx context.Context, components ...Component) *ServiceLifecycle { | ||
if s.failure { | ||
return s | ||
} | ||
for _, c := range components { | ||
select { | ||
case <-s.innerCtx.Done(): | ||
s.failure = true | ||
return s | ||
default: | ||
} | ||
if err := c.Init(ctx); err != nil { | ||
log.Panicf("Init %s error: %v", c.Name(), err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Panic? or Does return error and log Warn better? |
||
s.failure = true | ||
} else { | ||
log.Infof("Init %s successfully!", c.Name()) | ||
} | ||
} | ||
return s | ||
} | ||
|
||
// StartServices starts running services | ||
func (s *ServiceLifecycle) StartServices(ctx context.Context, services ...Service) *ServiceLifecycle { | ||
if s.failure { | ||
return s | ||
} | ||
s.services = append(s.services, services...) | ||
for _, service := range services { | ||
select { | ||
case <-s.innerCtx.Done(): | ||
s.failure = true | ||
return s | ||
default: | ||
} | ||
go s.start(ctx, service) | ||
} | ||
return s | ||
} | ||
|
||
func (s *ServiceLifecycle) start(ctx context.Context, service Service) { | ||
defer s.innerCancel() | ||
if err := service.Start(ctx); err != nil { | ||
log.Panicf("Service %s starts error: %v", service.Name(), err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does return error and log Warn better? |
||
} else { | ||
log.Infof("Service %s starts successfully", service.Name()) | ||
} | ||
} | ||
|
||
// Signals registers monitor signals | ||
func (s *ServiceLifecycle) Signals(sigs ...os.Signal) *ServiceLifecycle { | ||
if s.failure { | ||
return s | ||
} | ||
go s.signals(sigs...) | ||
return s | ||
} | ||
|
||
func (s *ServiceLifecycle) signals(sigs ...os.Signal) { | ||
sigCh := make(chan os.Signal, 1) | ||
signal.Notify(sigCh, sigs...) | ||
for { | ||
select { | ||
case <-s.innerCtx.Done(): | ||
return | ||
case sig := <-sigCh: | ||
for _, j := range sigs { | ||
if j == sig { | ||
s.innerCancel() | ||
return | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Wait blocks until context is done | ||
func (s *ServiceLifecycle) Wait(ctx context.Context) { | ||
<-s.innerCtx.Done() | ||
s.GracefulShutdown(ctx) | ||
} | ||
|
||
// GracefulShutdown can stop services when context is done or timeout | ||
func (s *ServiceLifecycle) GracefulShutdown(ctx context.Context) { | ||
gCtx, cancel := context.WithTimeout(context.Background(), s.timeout) | ||
go s.stopService(ctx, cancel) | ||
|
||
<-gCtx.Done() | ||
if errors.Is(gCtx.Err(), context.Canceled) { | ||
log.Infow("Service graceful shutdown", "service config timeout", s.timeout) | ||
} else if errors.Is(gCtx.Err(), context.DeadlineExceeded) { | ||
log.Panic("Timeout while stopping service, killing instance manually") | ||
} | ||
} | ||
|
||
func (s *ServiceLifecycle) stopService(ctx context.Context, cancel context.CancelFunc) { | ||
var wg sync.WaitGroup | ||
for _, service := range s.services { | ||
wg.Add(1) | ||
go func(ctx context.Context, service Service) { | ||
defer wg.Done() | ||
if err := service.Stop(ctx); err != nil { | ||
log.Panicf("Service %s stops failure: %v", service.Name(), err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let all the service run stop? the back service may have important things to do,e.g. store the stateful data to db. |
||
} else { | ||
log.Infof("Service %s stops successfully!", service.Name()) | ||
} | ||
}(ctx, service) | ||
} | ||
wg.Wait() | ||
cancel() | ||
} | ||
|
||
// Done check context is done | ||
func (s *ServiceLifecycle) Done() <-chan struct{} { | ||
return s.innerCtx.Done() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what the different between service and component?