Skip to content

Commit

Permalink
v1.5.1: LatestRelease <- Development (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmbek authored Jul 29, 2024
2 parents b7b97da + 1632af5 commit 03ce55e
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 50 deletions.
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
# Graceful Go Service (GGService)
GGService is a Go package designed for building robust and gracefully shutdown services. It provides a framework to easily manage service lifecycle, handle interrupts, and ensure smooth operations even during shutdowns. Ideal for applications requiring reliable service management. Contains graceful shutdowns and custom functions.

ggservice version 1.5.1 <br>
go version 1.22+ <br>
current test coverage: 63.4% <br>
[![Go report][go_report_img]][go_report_url]

## Features

- **Graceful Shutdowns:** GgService allows services to handle interrupts and shutdowns gracefully, ensuring minimal disruption.
- **Customizable:** Easily integrate with your Go applications by providing custom start and run functions.
- **Simple API:** Straightforward API for starting, stopping, and managing service lifecycles.
- **Application Lifecycle** You can start, restart, stop or force shutdowns. Multiple services can run simultaneous, but be aware that the lowest default timeout of a force shutdown will force shutdown the whole application.

## Installation

To use GGService in your Go project, simply run:

```bash
go get github.com/lmbek/ggservice
```

## Example code (How to use)
GGService can be used like this:

1) Create main.go file
2) Create go.mod file with your chosen module name and go version
3) Insert the code into main.go
```bash
package main

Expand Down Expand Up @@ -88,26 +93,19 @@ func forceShutdown() error {
os.Exit(-1)
return nil
}

// run loops from the application is started until it is stopped, terminated or ForceShutdown (please use with time.Sleep in between frames)
func run2() error {
log.Println("start of work2")
time.Sleep(894 * time.Millisecond) // note: if the graceful timer duration is below amount of work needed to be done, it will forceExit
log.Println("end of work2")
return nil
}

// run loops from the application is started until it is stopped, terminated or ForceShutdown (please use with time.Sleep in between frames)
func run3() error {
log.Println("work3 (with custom loop time.Sleep)")
return nil
}


```
4) run go mod tidy or go get github.com/lmbek/ggservice to import ggservice
```bash
go mod tidy
```
5) run the command
```bash
go run .
```
## Contributors
Lars M Bek (https://github.com/lmbek)
Ida Marcher Jensen (https://github.com/notHooman996)
## License
Expand Down
2 changes: 1 addition & 1 deletion example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module example

go 1.22

require github.com/lmbek/ggservice v1.4.0
require github.com/lmbek/ggservice v1.5.1

replace github.com/lmbek/ggservice => ..
13 changes: 6 additions & 7 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,10 @@ func (s *Service) Restart() error {
if s.logLevel >= LOG_LEVEL_INFO {
time.Sleep(20 * time.Millisecond) // to prevent log package from race condition logging most of the time
log.Println("Calling for restart of service: " + s.Name)
err := s.Stop() // ignore stop err
if err != nil {
log.Println(err)
}

}
err := s.Stop() // ignore stop err
if err != nil {
log.Println(err)
}

for {
Expand Down Expand Up @@ -237,7 +236,7 @@ func (s *Service) listenForInterrupt(forceShutdown func() error) {
s.isInterrupted = true
// printing interrupt signal warning regardless of s.PrintLog
if s.logLevel >= LOG_LEVEL_WARN {
log.Printf("%s received interrupt signal, initiating graceful shutdown (timeout: %v)\n", s.Name, s.gracefulShutdownTime)
log.Printf("%s received interrupt signal, initiating graceful shutdown (timeout: %v)\n", s.Name, s.GetGracefulShutdownTime())
}

signal.Stop(osSignal)
Expand All @@ -250,7 +249,7 @@ func (s *Service) listenForInterrupt(forceShutdown func() error) {

// Schedule a forced shutdown if the graceful shutdown time elapses
go func() {
<-time.After(s.gracefulShutdownTime)
<-time.After(s.GetGracefulShutdownTime())

// Custom forceShutdown func if provided
if forceShutdown != nil {
Expand Down
204 changes: 180 additions & 24 deletions service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,186 @@ import (
"time"
)

// TESTS:

var startFunc = func() error {
fmt.Println("started service...")
return nil
}

var runFunc = func() error {
fmt.Println("running service...")
time.Sleep(1 * time.Second)
return nil
}

var stopFunc = func() error {
fmt.Println("stopped service...")
time.Sleep(1 * time.Second)
return nil
}

func TestNew(test *testing.T) {
service := ggservice.New(&ggservice.Service{Name: "My Service"})
if service == nil {
test.Error("Could not create new service")
}
}

func TestNewService(t *testing.T) {
service := ggservice.NewService("My Service")
if service == nil {
t.Error("Could not create new service")
}
}

func TestService_StartStop(t *testing.T) {
t.Run("Without custom functions", func(t *testing.T) {
service := ggservice.NewService("My Service")
err := service.Start(nil, nil, nil, nil)
if err != nil {
t.Error(err)
}
})
t.Run("With start function", func(t *testing.T) {
service := ggservice.NewService("My Service")

err := service.Start(startFunc, nil, nil, nil)
if err != nil {
t.Error(err)
}
})
t.Run("With stop function", func(t *testing.T) {
service := ggservice.NewService("My Service")

err := service.Start(nil, nil, stopFunc, nil)
if err != nil {
t.Error(err)
}
})
// waitgroup dependent below:
t.Run("With custom functions", func(t *testing.T) {
waitgroup := sync.WaitGroup{}
waitgroup.Add(2)
var service ggservice.IService

go func() {
defer waitgroup.Done()
service = ggservice.NewService("My Service")
err := service.Start(nil, runFunc, nil, nil)
if err != nil {
t.Error(err)
}
}()
go func() {
defer waitgroup.Done()
time.Sleep(3 * time.Second)
err := service.Stop()
if err != nil {
t.Error(err)
}
}()
waitgroup.Wait()
})
t.Run("With full custom functions", func(t *testing.T) {
waitgroup := sync.WaitGroup{}
waitgroup.Add(2)
var service2 ggservice.IService

forceShutdownFunc := func() error {
fmt.Println("stopped service...")
time.Sleep(1 * time.Second)
return nil
}

go func() {
defer waitgroup.Done()
service2 = ggservice.NewService("My Service")
err := service2.Start(startFunc, runFunc, stopFunc, forceShutdownFunc)
if err != nil {
t.Error(err)
}
}()

go func() {
defer waitgroup.Done()
time.Sleep(3 * time.Second)
err := service2.Stop()
if err != nil {
t.Error(err)
}
}()
waitgroup.Wait()
})
}

func TestService_Restart(t *testing.T) {
service := ggservice.NewService("My Service")

testFunc := func() {
waitgroup := sync.WaitGroup{}

go func() {
waitgroup.Add(1)
defer waitgroup.Done()
time.Sleep(3 * time.Second)
err := service.Restart() // this is a blocking call
if err != nil {
t.Error(err)
}
}()
go func() {
waitgroup.Add(1)
defer waitgroup.Done()
time.Sleep(6 * time.Second)
err := service.Stop()
if err != nil {
t.Error(err)
}
}()
err := service.Start(nil, runFunc, nil, nil) // this is a blocking call
waitgroup.Wait()
if err != nil {
t.Error(err)
}
}

t.Run("with_logLevel_default", func(t *testing.T) {
testFunc()
})

t.Run("with_logLevel_all", func(t *testing.T) {
service.SetLogLevel(ggservice.LOG_LEVEL_ALL)
testFunc()
})

t.Run("with_logLevel_warn", func(t *testing.T) {
service.SetLogLevel(ggservice.LOG_LEVEL_WARN)
testFunc()
})

t.Run("with_logLevel_error", func(t *testing.T) {
service.SetLogLevel(ggservice.LOG_LEVEL_ERROR)
testFunc()
})

t.Run("with_logLevel_none", func(t *testing.T) {
service.SetLogLevel(ggservice.LOG_LEVEL_NONE)
testFunc()
})

}

func TestService_ForceShutdown(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestService_listenForInterrupt(t *testing.T) {
t.Errorf("test not implemented yet")
}

// EXAMPLES:

func ExampleNewService() {
service := ggservice.NewService("My Service")

Expand Down Expand Up @@ -69,30 +249,6 @@ func ExampleNew() {
}
}

func TestNew(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestNewService(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestService_ForceShutdown(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestService_Start(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestService_Stop(t *testing.T) {
t.Errorf("test not implemented yet")
}

func TestService_listenForInterrupt(t *testing.T) {
t.Errorf("test not implemented yet")
}

func ExampleService_Restart() {
ExampleStart := func() error {
fmt.Println("this runs when service starts")
Expand Down

0 comments on commit 03ce55e

Please sign in to comment.