Skip to content

Commit

Permalink
feat: Add API to get SDK's App Context
Browse files Browse the repository at this point in the history
Use the App Context in custom app service to exit long running functions when the context is
canceled due to termination signal.

Signed-off-by: Leonard Goodell <leonard.goodell@intel.com>
  • Loading branch information
Leonard Goodell committed Jun 27, 2023
1 parent 34e8d8f commit 7d6e55d
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 0 deletions.
6 changes: 6 additions & 0 deletions app-service-template/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package main

import (
"context"
"os"
"reflect"

Expand All @@ -38,6 +39,7 @@ const (
type myApp struct {
service interfaces.ApplicationService
lc logger.LoggingClient
appCtx context.Context
serviceConfig *config.ServiceConfig
configChanged chan bool
}
Expand Down Expand Up @@ -140,6 +142,10 @@ func (app *myApp) CreateAndRunAppService(serviceKey string, newServiceFactory fu
return -1
}

// TODO: Use this context in long running function to detect when the context is cancel for function can exit.
// Remove if no long running functions
app.appCtx = app.service.AppContext()

if err := app.service.Run(); err != nil {
app.lc.Errorf("Run returned error: %s", err.Error())
return -1
Expand Down
5 changes: 5 additions & 0 deletions app-service-template/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package main

import (
"context"
"fmt"
"testing"

Expand All @@ -39,6 +40,7 @@ func TestCreateAndRunService_Success(t *testing.T) {

mockFactory := func(_ string) (interfaces.ApplicationService, bool) {
mockAppService := &mocks.ApplicationService{}
mockAppService.On("AppContext").Return(context.Background())
mockAppService.On("LoggingClient").Return(logger.NewMockClient())
mockAppService.On("GetAppSettingStrings", "DeviceNames").
Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil)
Expand Down Expand Up @@ -81,6 +83,7 @@ func TestCreateAndRunService_GetAppSettingStrings_Failed(t *testing.T) {
getAppSettingStringsCalled := false
mockFactory := func(_ string) (interfaces.ApplicationService, bool) {
mockAppService := &mocks.ApplicationService{}
mockAppService.On("AppContext").Return(context.Background())
mockAppService.On("LoggingClient").Return(logger.NewMockClient())
mockAppService.On("GetAppSettingStrings", "DeviceNames").
Return(nil, fmt.Errorf("Failed")).Run(func(args mock.Arguments) {
Expand All @@ -104,6 +107,7 @@ func TestCreateAndRunService_SetFunctionsPipeline_Failed(t *testing.T) {

mockFactory := func(_ string) (interfaces.ApplicationService, bool) {
mockAppService := &mocks.ApplicationService{}
mockAppService.On("AppContext").Return(context.Background())
mockAppService.On("LoggingClient").Return(logger.NewMockClient())
mockAppService.On("GetAppSettingStrings", "DeviceNames").
Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil)
Expand Down Expand Up @@ -137,6 +141,7 @@ func TestCreateAndRunService_Run_Failed(t *testing.T) {

mockFactory := func(_ string) (interfaces.ApplicationService, bool) {
mockAppService := &mocks.ApplicationService{}
mockAppService.On("AppContext").Return(context.Background())
mockAppService.On("LoggingClient").Return(logger.NewMockClient())
mockAppService.On("GetAppSettingStrings", "DeviceNames").
Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil)
Expand Down
6 changes: 6 additions & 0 deletions internal/app/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ type contextGroup struct {
stop context.CancelFunc
}

// AppContext returns the application service context used to detect cancelled context when the service is terminating.
// Used by custom app service to appropriately exit any long-running functions.
func (svc *Service) AppContext() context.Context {
return svc.ctx.appCtx
}

// AddRoute allows you to leverage the existing webserver to add routes.
func (svc *Service) AddRoute(route string, handler func(nethttp.ResponseWriter, *nethttp.Request), methods ...string) error {
if route == commonConstants.ApiPingRoute ||
Expand Down
13 changes: 13 additions & 0 deletions internal/app/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package app

import (
"context"
"fmt"
"net/http"
"os"
Expand Down Expand Up @@ -964,3 +965,15 @@ func TestService_SecretProvider(t *testing.T) {
require.NotNil(t, actual)
assert.Equal(t, mockSecretProvider, actual)
}

func TestService_AppContext(t *testing.T) {
expected, _ := context.WithCancel(context.Background())
sdk := Service{
ctx: contextGroup{
appCtx: expected,
},
}

actual := sdk.AppContext()
assert.Equal(t, expected, actual)
}
18 changes: 18 additions & 0 deletions pkg/interfaces/mocks/ApplicationService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/interfaces/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package interfaces

import (
"context"
"net/http"
"time"

Expand Down Expand Up @@ -75,6 +76,9 @@ type UpdatableConfig interface {

// ApplicationService defines the interface for an edgex Application Service
type ApplicationService interface {
// AppContext returns the application service context used to detect cancelled context when the service is terminating.
// Used by custom app service to appropriately exit any long-running functions.
AppContext() context.Context
// AddRoute a custom REST route to the application service's internal webserver
// A reference to this ApplicationService is add the the context that is passed to the handler, which
// can be retrieved using the `AppService` key
Expand Down

0 comments on commit 7d6e55d

Please sign in to comment.