-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager_internal.go
179 lines (159 loc) · 5 KB
/
manager_internal.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
package sup
// this file contains the state machine functions for the inner workings of manager
/*
The maintainence actor.
Your controller strategy code is running in another goroutine. This one
is in charge of operations like collecting child status, and is
purely internal so it can reliably handle its own blocking behavior.
*/
func (mgr *manager) run() {
log(mgr.reportingTo.Name(), "working", nil, false)
defer log(mgr.reportingTo.Name(), "all done", nil, false)
stepFn := mgr.step_Accepting
for {
if stepFn == nil {
break
}
stepFn = stepFn()
}
}
/*
Steps in the state machine of the supervisor's internal maint.
This pattern is awfully nice:
- you can see the transitions by clear name (returns highlight them)
- you *don't* see the visual clutter of code for transitions that are
not possible for whatever state you're currently looking at
- even if things really go poorly, your stack trace clearly indicates
exactly which state you were in (it's in the function name after all).
*/
type mgr_step func() mgr_step
/*
During Accepting, new requests for writs will be accepted freely.
This function gathers childDone signals and waits for quit or winddown instructions.
*/
func (mgr *manager) step_Accepting() mgr_step {
select {
case childDone := <-mgr.ctrlChan_childDone:
mgr.reapChild(childDone)
return mgr.step_Accepting
case <-mgr.ctrlChan_quit.Selectable():
mgr.stopAccepting()
mgr.cancelAll()
return mgr.step_Quitting
case <-mgr.reportingTo.QuitCh():
mgr.stopAccepting()
mgr.cancelAll()
return mgr.step_Quitting
case <-mgr.ctrlChan_winddown.Selectable():
mgr.stopAccepting()
return mgr.step_Winddown
}
}
/*
During Winddown, new requests for writs will be handled, but immediately rejected.
Winddown is reached either by setting the manager to accept no new work.
Winddown loops until all wards have been gathered,
then we make the final transition: to terminated.
Quits during winddown take us to the Quitting phase, which is mostly
identical except for obvious reasons doesn't have to keep waiting for
the potential of a quit signal.
*/
func (mgr *manager) step_Winddown() mgr_step {
if len(mgr.wards) == 0 {
return mgr.step_Terminated
}
select {
case childDone := <-mgr.ctrlChan_childDone:
mgr.reapChild(childDone)
return mgr.step_Winddown
case <-mgr.ctrlChan_quit.Selectable():
mgr.cancelAll()
return mgr.step_Quitting
case <-mgr.reportingTo.QuitCh():
mgr.cancelAll()
return mgr.step_Quitting
}
}
/*
During Quitting, behavior is about as per Winddown, but we've also...
well, quit.
There's no significant difference to this phase, other than that we no
long select on either the winddown or quit transitions.
*/
func (mgr *manager) step_Quitting() mgr_step {
if len(mgr.wards) == 0 {
return mgr.step_Terminated
}
select {
case childDone := <-mgr.ctrlChan_childDone:
mgr.reapChild(childDone)
return mgr.step_Quitting
}
}
/*
During Termination, we do some final housekeeping real quick, signaling
our completion and then...
that's it.
*/
func (mgr *manager) step_Terminated() mgr_step {
// Let others see us as done. yayy!
mgr.doneFuse.Fire()
// We've finally stopped selecting. We're done. We're out.
// No other goroutines alive should have reach to this channel, so we can close it.
close(mgr.ctrlChan_childDone)
// It's over. No more step functions to call.
return nil
}
//// actions
/*
Release a new writ, appending to wards -- or, if in any state other
than accepting, return a thunk implement writ but rejecting any work.
This is the only action that can be called from outside the maint actor.
*/
func (mgr *manager) releaseWrit(name string) Writ {
mgr.mu.Lock()
defer mgr.mu.Unlock()
// No matter what, we're responding, and it earns a name.
writName := mgr.reportingTo.Name().New(name)
// If outside of the accepting states, reject by responding with a doa writ.
if !mgr.accepting {
log(mgr.reportingTo.Name(), "manager rejected writ requisition", writName, false)
// Send back an unusable monad.
return &writ{
name: writName,
phase: int32(WritPhase_Terminal),
}
}
// Ok, we're doing it: make a new writ to track this upcoming task.
log(mgr.reportingTo.Name(), "manager releasing writ", writName, false)
wrt := newWrit(writName)
// Assign our final report hook to call back home.
wrt.afterward = func() {
log(mgr.reportingTo.Name(), "writ turning in", writName, false)
mgr.ctrlChan_childDone <- wrt
}
// Register it.
mgr.wards[wrt] = wrt.quitFuse.Fire
// Release it into the wild.
return wrt
}
func (mgr *manager) stopAccepting() {
mgr.mu.Lock()
mgr.accepting = false
mgr.mu.Unlock()
}
func (mgr *manager) reapChild(childDone Writ) {
mgr.mu.Lock()
defer mgr.mu.Unlock()
log(mgr.reportingTo.Name(), "reaped child", childDone.Name(), false)
delete(mgr.wards, childDone)
mgr.tombstones.Push(childDone)
}
func (mgr *manager) cancelAll() {
mgr.mu.Lock()
defer mgr.mu.Unlock()
log(mgr.reportingTo.Name(), "manager told to cancel all!", nil, false)
for _, cancelFn := range mgr.wards {
cancelFn()
}
}