-
-
Notifications
You must be signed in to change notification settings - Fork 57
/
service.go
150 lines (124 loc) 路 3.55 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
package service
import (
"context"
"sync"
"github.com/pkg/errors"
"github.com/dennis-tra/pcp/internal/log"
)
// State represents the lifecycle states of a service.
type State uint8
const (
// These are the concrete lifecycle manifestations.
Idle State = iota
Started
Stopping
Stopped
)
// ErrServiceAlreadyStarted is returned if there are multiple calls to ServiceStarted.
// If this happens somethings wrong :/
var ErrServiceAlreadyStarted = errors.New("the service was already started in the past")
// Service represents an entity that runs in a
// separate go routine and where its lifecycle
// needs to be handled externally.
type Service struct {
// The name of the service for logging purposes
name string
// A context that can be used for long running
// io operations of the service. This context
// gets cancelled when the service receives a
// shutdown signal. It's controversial to store
// a context in a struct field but I believe
// that it makes sense here.
ctx context.Context
cancel context.CancelFunc
// The current state of this service.
lk sync.RWMutex
state State
// When a message is sent to this channel it
// starts to gracefully shut down.
shutdown chan struct{}
// When a message is sent to this channel
// the service has shut down.
done chan struct{}
}
// New instantiates an initialised Service struct. It
// deliberately does not accept a context as an input
// parameter as I consider long running service life-
// cycle handling with contexts as a bad practice.
// Contexts belong in request/response paths and
// Services should be handled via channels.
func New(name string) *Service {
ctx, cancel := context.WithCancel(context.Background())
return &Service{
ctx: ctx,
name: name,
cancel: cancel,
state: Idle,
shutdown: make(chan struct{}),
done: make(chan struct{}),
}
}
// ServiceStarted marks this service as started.
func (s *Service) ServiceStarted() error {
log.Debugln(s.name, "- Service has started")
s.lk.Lock()
defer s.lk.Unlock()
if s.state != Idle {
return ErrServiceAlreadyStarted
}
s.state = Started
go func() {
select {
case <-s.shutdown:
case <-s.done:
}
s.cancel()
}()
return nil
}
// SigShutdown exposes the shutdown channel to listen for
// shutdown instructions.
func (s *Service) SigShutdown() chan struct{} {
return s.shutdown
}
// SigDone exposes the done channel to listen for
// service termination.
func (s *Service) SigDone() chan struct{} {
return s.done
}
// ServiceStopped marks this service as stopped and
// ultimately releases an external call to Shutdown.
func (s *Service) ServiceStopped() {
s.lk.Lock()
defer s.lk.Unlock()
if s.state == Idle || s.state == Stopped {
return
}
s.state = Stopped
close(s.done)
log.Debugln(s.name, "- Service has stopped")
}
// ServiceContext returns the context associated with this
// service. This context is passed into requests or similar
// that are initiated from this service. Doing it this way
// we can cancel this contexts when someone shuts down
// the service, which results in all requests being stopped.
func (s *Service) ServiceContext() context.Context {
return s.ctx
}
// Shutdown instructs the service to gracefully shut down.
// This function blocks until the done channel was closed
// which happens when ServiceStopped is called.
func (s *Service) Shutdown() {
log.Debugln(s.name, "- Service shutting down...")
s.lk.Lock()
if s.state != Started {
s.lk.Unlock()
return
}
s.state = Stopping
s.lk.Unlock()
close(s.shutdown)
<-s.done
log.Debugln(s.name, "- Service was shut down")
}