blob: 1cbedbec5fefda1d6cd5ba9762b9b16a02029f40 [file] [log] [blame]
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Leopold Schabel68c58752019-11-14 21:00:59 +010017package service
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020018
19import (
Serge Bazanskicdb8c782020-02-17 12:34:02 +010020 "context"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020021 "errors"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020022 "sync"
Serge Bazanskicdb8c782020-02-17 12:34:02 +010023
24 "go.uber.org/zap"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020025)
26
27var (
28 ErrAlreadyRunning = errors.New("service is already running")
29 ErrNotRunning = errors.New("service is not running")
30)
31
32type (
33 // Service represents a subsystem of an application that can be used with a BaseService.
34 Service interface {
35 OnStart() error
36 OnStop() error
37 }
38
39 // BaseService implements utility functionality around a service.
40 BaseService struct {
41 impl Service
42 name string
43
44 Logger *zap.Logger
45
46 mutex sync.Mutex
47 running bool
Serge Bazanskicdb8c782020-02-17 12:34:02 +010048
49 // A context that represents the lifecycle of a service.
50 // It is created right before impl.OnStart, and canceled
51 // right after impl.OnStop is.
52 // This is a transition mechanism from moving from OnStart/OnStop
53 // based lifecycle management of services to a context-based supervision
54 // tree.
55 // Service implementations should access this via .Context()
56 ctx *context.Context
57 ctxC *context.CancelFunc
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020058 }
59)
60
61func NewBaseService(name string, logger *zap.Logger, impl Service) *BaseService {
62 return &BaseService{
63 Logger: logger,
64 name: name,
65 impl: impl,
Serge Bazanskicdb8c782020-02-17 12:34:02 +010066 ctx: nil,
67 ctxC: nil,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020068 }
69}
70
71// Start starts the service. This is an atomic operation and should not be called on an already running service.
72func (b *BaseService) Start() error {
73 b.mutex.Lock()
74 defer b.mutex.Unlock()
75
76 if b.running {
77 return ErrAlreadyRunning
78 }
79
Serge Bazanskicdb8c782020-02-17 12:34:02 +010080 ctx, ctxC := context.WithCancel(context.Background())
81 b.ctx = &ctx
82 b.ctxC = &ctxC
83
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020084 err := b.impl.OnStart()
85 if err != nil {
86 b.Logger.Error("Failed to start service", zap.String("service", b.name), zap.Error(err))
87 return err
88 }
89
90 b.running = true
91 b.Logger.Info("Started service", zap.String("service", b.name))
92 return nil
93}
94
Leopold Schabel68c58752019-11-14 21:00:59 +010095// Stop stops the service. This is an atomic operation and should only be called on a running service.
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020096func (b *BaseService) Stop() error {
97 b.mutex.Lock()
98 defer b.mutex.Unlock()
99
100 if !b.running {
101 return ErrNotRunning
102 }
103
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100104 err := b.impl.OnStop()
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200105 if err != nil {
106 b.Logger.Error("Failed to stop service", zap.String("service", b.name), zap.Error(err))
107
108 return err
109 }
110
111 b.running = false
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100112
113 // Kill context
114 (*b.ctxC)()
115 b.ctx = nil
116 b.ctxC = nil
117
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200118 b.Logger.Info("Stopped service", zap.String("service", b.name))
119 return nil
120}
121
122// IsRunning returns whether the service is currently running.
123func (b *BaseService) IsRunning() bool {
124 b.mutex.Lock()
125 defer b.mutex.Unlock()
126
127 return b.running
128}
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100129
130// Context returns a context that can be used within OnStart() to create new
131// lightweight subservices that use a context for lifecycle management.
132// This is a transition measure before the Service library is rewritten to use
133// a more advanced context-and-returned-error supervision tree.
134// This context can also be used for blocking operations like IO, etc.
135func (b *BaseService) Context() context.Context {
136 return *b.ctx
137}