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

add service lifecycle module #9

Merged
merged 13 commits into from
Dec 26, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
28 changes: 28 additions & 0 deletions pkg/lifecycle/component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lifecycle

import "context"
Copy link
Contributor

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?


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)
}
153 changes: 153 additions & 0 deletions pkg/lifecycle/lifecycle.go
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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

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