forked from sei-protocol/sei-tendermint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
203 lines (164 loc) · 4.34 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package service
import (
"context"
"errors"
"sync"
"github.com/badrootd/sei-tendermint/libs/log"
)
var (
// errAlreadyStopped is returned when somebody tries to stop an already
// stopped service (without resetting it).
errAlreadyStopped = errors.New("already stopped")
_ Service = (*BaseService)(nil)
)
// Service defines a service that can be started, stopped, and reset.
type Service interface {
// Start is called to start the service, which should run until
// the context terminates. If the service is already running, Start
// must report an error.
Start(context.Context) error
// Manually terminates the service
Stop()
// Return true if the service is running
IsRunning() bool
// Wait blocks until the service is stopped.
Wait()
}
// Implementation describes the implementation that the
// BaseService implementation wraps.
type Implementation interface {
// Called by the Services Start Method
OnStart(context.Context) error
// Called when the service's context is canceled.
OnStop()
}
/*
Classical-inheritance-style service declarations. Services can be started, then
stopped, but cannot be restarted.
Users must implement OnStart/OnStop methods. In the absence of errors, these
methods are guaranteed to be called at most once. If OnStart returns an error,
service won't be marked as started, so the user can call Start again.
The BaseService implementation ensures that the OnStop method is
called after the context passed to Start is canceled.
Typical usage:
type FooService struct {
BaseService
// private fields
}
func NewFooService() *FooService {
fs := &FooService{
// init
}
fs.BaseService = *NewBaseService(log, "FooService", fs)
return fs
}
func (fs *FooService) OnStart(ctx context.Context) error {
// initialize private fields
// start subroutines, etc.
}
func (fs *FooService) OnStop() {
// close/destroy private fields and releases resources
}
*/
type BaseService struct {
logger log.Logger
name string
mtx sync.Mutex
quit <-chan (struct{})
cancel context.CancelFunc
// The "subclass" of BaseService
impl Implementation
}
// NewBaseService creates a new BaseService.
func NewBaseService(logger log.Logger, name string, impl Implementation) *BaseService {
return &BaseService{
logger: logger,
name: name,
impl: impl,
}
}
// Start starts the Service and calls its OnStart method. An error
// will be returned if the service is stopped, but not if it is
// already running.
func (bs *BaseService) Start(ctx context.Context) error {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit != nil {
return nil
}
select {
case <-bs.quit:
return errAlreadyStopped
default:
bs.logger.Info("starting service", "service", bs.name, "impl", bs.name)
if err := bs.impl.OnStart(ctx); err != nil {
return err
}
// we need a separate context to ensure that we start
// a thread that will get cleaned up and that the
// Stop/Wait functions work as expected.
srvCtx, cancel := context.WithCancel(context.Background())
bs.cancel = cancel
bs.quit = srvCtx.Done()
go func(ctx context.Context) {
select {
case <-srvCtx.Done():
// this means stop was called manually
return
case <-ctx.Done():
bs.Stop()
}
bs.logger.Info("stopped service",
"service", bs.name)
}(ctx)
return nil
}
}
// Stop manually terminates the service by calling OnStop method from
// the implementation and releases all resources related to the
// service.
func (bs *BaseService) Stop() {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
return
}
select {
case <-bs.quit:
return
default:
bs.logger.Info("stopping service", "service", bs.name)
bs.impl.OnStop()
bs.cancel()
return
}
}
// IsRunning implements Service by returning true or false depending on the
// service's state.
func (bs *BaseService) IsRunning() bool {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
return false
}
select {
case <-bs.quit:
return false
default:
return true
}
}
func (bs *BaseService) getWait() <-chan struct{} {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
out := make(chan struct{})
close(out)
return out
}
return bs.quit
}
// Wait blocks until the service is stopped.
func (bs *BaseService) Wait() { <-bs.getWait() }
// String provides a human-friendly representation of the service.
func (bs *BaseService) String() string { return bs.name }