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

refactor eventHandlers into EventManager #3

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
29 changes: 19 additions & 10 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@ package vabastegi

import "time"

// Event is what Vabastegi event look like.
// Event is what Vabastegi event looks like.
type Event interface {
event() // it's private to prevent outside implementation.
}

// EventHandlers is list of EventHandler.
type EventHandlers []EventHandler
// eventManager responsible to manage the event system.
type eventManager struct {
handlers []EventHandler
}

// newEventManager create a new instance of eventManager.
func newEventManager(handlers []EventHandler) *eventManager {
return &eventManager{handlers: handlers}
}

// Publish passed event using event handlers.
func (e EventHandlers) Publish(event Event) {
for _, handler := range e {
func (e *eventManager) Publish(event Event) {
for _, handler := range e.handlers {
handler.OnEvent(event)
}
}

// Register event handler.
func (e *eventManager) Register(handler EventHandler) {
e.handlers = append(e.handlers, handler)
}

// EventHandler used if you need to handle the events.
type EventHandler interface {
OnEvent(event Event)
Expand Down Expand Up @@ -106,15 +118,12 @@ type OnApplicationShutdownExecuting struct {
// ShutdownAt is the time shutdown happened.
ShutdownAt time.Time

// Reason is the reason for shutdown the application.
Reason string
// Cause is the reason for shutdown the application.
Cause error
}

// OnApplicationShutdownExecuted is emitted after the application Shutdown has been executed.
type OnApplicationShutdownExecuted struct {
// Reason is the reason for shutdown the application.
Reason string

// Runtime specifies how long it took to run this hook.
Runtime time.Duration

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/mrsoftware/vabastegi

go 1.18.0

require github.com/mrsoftware/errors v0.1.0
require github.com/mrsoftware/errors v0.2.0-alpha.3
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mrsoftware/errors v0.1.0 h1:5MSHsrsqlBMPbNzwobVt39IpgsVee7LuSe+n8aQWPsI=
github.com/mrsoftware/errors v0.1.0/go.mod h1:iHqx83gamUM9jhiV/rWZuVZe54NVqtqkIDnvZHywSM8=
github.com/mrsoftware/errors v0.2.0-alpha.3 h1:T6UTulgnvEwUF+naTksmd6bO2tHb3hrTKdMm0Wxc0SI=
github.com/mrsoftware/errors v0.2.0-alpha.3/go.mod h1:iHqx83gamUM9jhiV/rWZuVZe54NVqtqkIDnvZHywSM8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
140 changes: 140 additions & 0 deletions lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package vabastegi

import (
"context"
"os/signal"
"reflect"
"runtime"
"strings"
"syscall"
"time"

"github.com/mrsoftware/errors"
)

// ShutdownListener is what you need to pass to OnShutdown method.
type ShutdownListener = func(ctx context.Context) error

// lifecycle manage application lifecycle like running task or shutdown.
type lifecycle struct {
listeners []ShutdownListener
publisher *eventManager
ctx context.Context
cancel context.CancelCauseFunc
waitGroup *errors.WaitGroup
}

// newLifecycle create a new instance of lifecycle.
func newLifecycle(ctx context.Context, publisher *eventManager) *lifecycle {
waitGroup := errors.NewWaitGroup(errors.WaitGroupWithContext(ctx), errors.WaitGroupWithStopOnError())

return &lifecycle{
listeners: make([]ShutdownListener, 0),
publisher: publisher,
ctx: waitGroup.Context(),
cancel: waitGroup.Stop,
waitGroup: waitGroup,
}
}

// Wait on shutdown or application finish.
func (l *lifecycle) Wait() error {
var err error
select {
case <-l.ctx.Done():
err = l.ctx.Err()
case err = <-errors.WaitChanel(l.waitGroup):
l.Stop(err)
}

return l.callShutdownListeners(l.ctx, err)
}

// RegisterGracefulShutdown start listing on os signal and cancel the parent context on getting one.
func (l *lifecycle) RegisterGracefulShutdown() {
ctx, cancel := signal.NotifyContext(l.ctx, syscall.SIGINT, syscall.SIGTERM)

l.ctx, l.cancel = ctx, cancelToCancelCause(cancel)
}

// Stop the application.
func (l *lifecycle) Stop(cause error) {
l.cancel(cause)
}

// GetContext of lifecycle.
func (l *lifecycle) GetContext() context.Context {
return l.ctx
}

// do Shut down the application.
func (l *lifecycle) callShutdownListeners(ctx context.Context, cause error) error {
errList := errors.NewMultiError()

startAt := time.Now()

l.publisher.Publish(&OnApplicationShutdownExecuting{
Cause: cause,
ShutdownAt: startAt,
})

defer func() {
l.publisher.Publish(&OnApplicationShutdownExecuted{
Runtime: time.Now().Sub(startAt),
Err: errList.Err(),
})
}()

for _, fn := range l.listeners {
errList.Add(l.shutdown(ctx, fn))
}

return errList.Err()
}

func (l *lifecycle) shutdown(ctx context.Context, callback ShutdownListener) (err error) {
startAt := time.Now()

l.publisher.Publish(&OnShutdownExecuting{
ProviderName: getProviderName(callback, 1),
CallerPath: getProviderName(callback, -1),
ShutdownAt: startAt,
})

defer func() {
l.publisher.Publish(&OnShutdownExecuted{
ProviderName: getProviderName(callback, 1),
CallerPath: getProviderName(callback, -1),
Runtime: time.Now().Sub(startAt),
Err: err,
})
}()

return callback(ctx)
}

// OnShutdown add callback to a list of listeners.
func (l *lifecycle) OnShutdown(callback ShutdownListener) {
l.listeners = append(l.listeners, callback)
}

// RunTask in the background.
func (l *lifecycle) RunTask(ctx context.Context, fn func(ctx context.Context) error) {
l.waitGroup.DoWithContext(ctx, fn)
}

func getProviderName(creator interface{}, index int) string {
reference := runtime.FuncForPC(reflect.ValueOf(creator).Pointer()).Name()
if index == -1 {
return reference
}

parts := strings.Split(reference, ".")

return parts[len(parts)-(1+index)]
}

// cancelToCancelCause is just a wrapper to turn context.CancelFunc into context.CancelCauseFunc.
func cancelToCancelCause(cancelFunc context.CancelFunc) context.CancelCauseFunc {
return func(cause error) { cancelFunc() }
}
2 changes: 1 addition & 1 deletion logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (l *EventLogger) OnEvent(event Event) {
case *OnShutdownExecuting:
l.log(InfoLogLevel, "Shutting Down %s", e.ProviderName)
case *OnApplicationShutdownExecuting:
l.log(InfoLogLevel, "Shutting Down Application: %s", e.Reason)
l.log(InfoLogLevel, "Shutting Down Application: %s", e.Cause)
case *OnLog:
l.log(e.Level, e.Message, e.Args...)
}
Expand Down
12 changes: 11 additions & 1 deletion options.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package vabastegi

import "context"

// Options of Vabastegi.
type Options struct {
GracefulShutdown bool
AppName string
EventHandlers EventHandlers
EventHandlers []EventHandler
Ctx context.Context
}

// Option of Vabastegi.
Expand All @@ -30,3 +33,10 @@ func WithEventHandlers(handlers ...EventHandler) Option {
options.EventHandlers = append(options.EventHandlers, handlers...)
}
}

// WithContext used if you need to pass custom context.
func WithContext(ctx context.Context) Option {
return func(options *Options) {
options.Ctx = ctx
}
}
Loading