Skip to content

Commit f2f34b3

Browse files
authored
feature: critical units (#17)
* feat: add "critical" and "success_codes" field to unit, #12 * feat: add SuccessCodes support to mexec * feat: update runner signature * feat: fexit if short unit failed with critical * feat: fexit if long running unit failed with critical
1 parent 51100a4 commit f2f34b3

File tree

13 files changed

+231
-99
lines changed

13 files changed

+231
-99
lines changed

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,17 @@ command:
128128
- once
129129
```
130130

131-
By default `once` units will block other `minit` units until finished.
131+
**Non-blocking**
132+
133+
By default, `once` units will block other `minit` units until finished.
132134

133135
Set `blocking: false` to run `once` units in background.
134136

137+
**Critical**
138+
139+
If `critical` field is set to `true`, `minit` will stop if this unit failed.
140+
141+
135142
### 3.3 Type: `daemon`
136143

137144
`daemon` units execute after `render` and `once`. It runs long-running command.
@@ -311,6 +318,31 @@ Example:
311318
MINIT_DISABLE=once-demo,@demo
312319
```
313320

321+
## 4.7 Critical Units
322+
323+
If `critical` field is set to `true`, `minit` will stop if this unit failed.
324+
325+
By specifying the `success_codes` field for `once`, `daemon` and `cron` units, `minit` will interpret exit codes within the provided list as indicative of success.
326+
327+
Example:
328+
329+
```yaml
330+
kind: once
331+
name: once-demo-critical
332+
critical: true
333+
command:
334+
- false
335+
---
336+
kind: once
337+
name: once-demo-critical
338+
critical: true
339+
success_codes:
340+
- 0
341+
- 1
342+
command:
343+
- false
344+
```
345+
314346
## 5. Extra Features
315347

316348
### 5.1 Zombie Processes Cleaning

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ require (
66
github.com/robfig/cron/v3 v3.0.1
77
github.com/stretchr/testify v1.9.0
88
github.com/yankeguo/rg v1.2.0
9-
golang.org/x/net v0.25.0
10-
golang.org/x/sys v0.20.0
11-
golang.org/x/text v0.15.0
9+
golang.org/x/net v0.26.0
10+
golang.org/x/sys v0.21.0
11+
golang.org/x/text v0.16.0
1212
gopkg.in/yaml.v3 v3.0.1
1313
)
1414

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
88
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
99
github.com/yankeguo/rg v1.2.0 h1:EhXUSK/BvO+d369Bk9CYQG2mQTWsgPKuhXHxd6KI8XM=
1010
github.com/yankeguo/rg v1.2.0/go.mod h1:OBQueah3CKlMksIbYKNGWJUF20pyy/mipY9NXDXlJ+c=
11-
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
12-
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
13-
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
14-
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
15-
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
16-
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
11+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
12+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
13+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
14+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
15+
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
16+
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
1717
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1818
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1919
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

main.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ func main() {
166166

167167
// execute short runners
168168
for _, runner := range runnersS {
169-
runner.Action.Do(context.Background())
169+
if err = runner.Action.Do(context.Background()); err != nil {
170+
return
171+
}
170172
}
171173

172174
// quick exit
@@ -178,11 +180,17 @@ func main() {
178180
// run long runners
179181
ctx, cancel := context.WithCancel(context.Background())
180182
wg := &sync.WaitGroup{}
183+
chErr := make(chan error, 1)
181184

182185
for _, runner := range runnersL {
183186
wg.Add(1)
184187
go func(runner mrunners.Runner) {
185-
runner.Action.Do(ctx)
188+
if err := runner.Action.Do(ctx); err != nil {
189+
select {
190+
case chErr <- err:
191+
default:
192+
}
193+
}
186194
wg.Done()
187195
}(runner)
188196
}
@@ -192,8 +200,19 @@ func main() {
192200
// wait for signals
193201
chSig := make(chan os.Signal, 1)
194202
signal.Notify(chSig, syscall.SIGINT, syscall.SIGTERM)
195-
sig := <-chSig
196-
log.Printf("signal caught: %s", sig.String())
203+
204+
var sig os.Signal
205+
206+
select {
207+
case sig = <-chSig:
208+
log.Printf("signal caught: %s", sig.String())
209+
case err = <-chErr:
210+
log.Printf("critical error caught: %s", err.Error())
211+
}
212+
213+
if sig == nil {
214+
sig = syscall.SIGTERM
215+
}
197216

198217
// shutdown context
199218
cancel()

pkg/mexec/manager.go

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mexec
22

33
import (
44
"errors"
5+
"fmt"
56
"io"
67
"os"
78
"os/exec"
@@ -19,14 +20,14 @@ import (
1920
type ExecuteOptions struct {
2021
Name string
2122

22-
Dir string
23-
Shell string
24-
Env map[string]string
25-
Command []string
26-
Charset string
23+
Dir string
24+
Shell string
25+
Env map[string]string
26+
Command []string
27+
Charset string
28+
SuccessCodes []int
2729

28-
Logger mlog.ProcLogger
29-
IgnoreExecError bool
30+
Logger mlog.ProcLogger
3031
}
3132

3233
type Manager interface {
@@ -35,42 +36,45 @@ type Manager interface {
3536
}
3637

3738
type manager struct {
38-
childPIDs map[int]struct{}
39-
childPIDLock sync.Locker
40-
charsets map[string]encoding.Encoding
39+
managedPIDs map[int]struct{}
40+
managedPIDLock sync.Locker
41+
charsets map[string]encoding.Encoding
4142
}
4243

4344
func NewManager() Manager {
4445
return &manager{
45-
childPIDs: map[int]struct{}{},
46-
childPIDLock: &sync.Mutex{},
46+
managedPIDs: map[int]struct{}{},
47+
managedPIDLock: &sync.Mutex{},
4748
charsets: map[string]encoding.Encoding{
4849
"gb18030": simplifiedchinese.GB18030,
4950
"gbk": simplifiedchinese.GBK,
5051
},
5152
}
5253
}
5354

54-
func (m *manager) addChildPID(fn func() (pid int, err error)) error {
55-
m.childPIDLock.Lock()
56-
defer m.childPIDLock.Unlock()
57-
pid, err := fn()
58-
if err == nil {
59-
m.childPIDs[pid] = struct{}{}
55+
func (m *manager) StartCommand(cmd *exec.Cmd) (done func(), err error) {
56+
m.managedPIDLock.Lock()
57+
defer m.managedPIDLock.Unlock()
58+
59+
if err = cmd.Start(); err != nil {
60+
return
6061
}
61-
return err
62-
}
6362

64-
func (m *manager) delChildPID(pid int) {
65-
m.childPIDLock.Lock()
66-
defer m.childPIDLock.Unlock()
67-
delete(m.childPIDs, pid)
63+
pid := cmd.Process.Pid
64+
m.managedPIDs[pid] = struct{}{}
65+
done = func() {
66+
m.managedPIDLock.Lock()
67+
defer m.managedPIDLock.Unlock()
68+
delete(m.managedPIDs, pid)
69+
}
70+
return
6871
}
6972

7073
func (m *manager) Signal(sig os.Signal) {
71-
m.childPIDLock.Lock()
72-
defer m.childPIDLock.Unlock()
73-
for pid := range m.childPIDs {
74+
m.managedPIDLock.Lock()
75+
defer m.managedPIDLock.Unlock()
76+
77+
for pid := range m.managedPIDs {
7478
if process, _ := os.FindProcess(pid); process != nil {
7579
_ = process.Signal(sig)
7680
}
@@ -148,15 +152,11 @@ func (m *manager) Execute(opts ExecuteOptions) (err error) {
148152
}
149153

150154
// start process in the same lock with signal children
151-
if err = m.addChildPID(func() (pid int, err error) {
152-
if err = cmd.Start(); err != nil {
153-
return
154-
}
155-
pid = cmd.Process.Pid
156-
return
157-
}); err != nil {
155+
var done func()
156+
if done, err = m.StartCommand(cmd); err != nil {
158157
return
159158
}
159+
defer done()
160160

161161
opts.Logger.Print("minit: " + opts.Name + ": process started")
162162

@@ -165,17 +165,40 @@ func (m *manager) Execute(opts ExecuteOptions) (err error) {
165165
go opts.Logger.Err().ReadFrom(errPipe)
166166

167167
// wait for process
168-
if err = cmd.Wait(); err != nil {
169-
opts.Logger.Error("minit: " + opts.Name + ": process exited with error: " + err.Error())
168+
err = cmd.Wait()
170169

171-
if opts.IgnoreExecError {
172-
err = nil
170+
var code int
171+
172+
if err != nil {
173+
if ee, ok := err.(*exec.ExitError); ok {
174+
code = ee.ExitCode()
175+
} else {
176+
opts.Logger.Error("minit: " + opts.Name + ": process exited with error: " + err.Error())
177+
return
173178
}
174-
} else {
175-
opts.Logger.Print("minit: " + opts.Name + ": process exited")
176179
}
177180

178-
m.delChildPID(cmd.Process.Pid)
181+
if checkSuccessCode(opts.SuccessCodes, code) {
182+
err = nil
183+
opts.Logger.Print("minit: " + opts.Name + ": process exited successfully")
184+
return
185+
}
186+
187+
err = fmt.Errorf("exit code: %d is not in success_codes", code)
188+
189+
opts.Logger.Error("minit: " + opts.Name + ": process exited with error: " + err.Error())
179190

180191
return
181192
}
193+
194+
func checkSuccessCode(successCodes []int, code int) bool {
195+
if len(successCodes) == 0 {
196+
return code == 0
197+
}
198+
for _, c := range successCodes {
199+
if c == code {
200+
return true
201+
}
202+
}
203+
return false
204+
}

pkg/mexec/manager_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ func TestNewManager(t *testing.T) {
3333
Command: []string{
3434
"echo", "$AAA",
3535
},
36-
Logger: logger,
37-
IgnoreExecError: true,
36+
Logger: logger,
3837
})
3938
require.NoError(t, err)
4039

@@ -56,8 +55,7 @@ func TestNewManager(t *testing.T) {
5655
Command: []string{
5756
"sleep", "$AAA",
5857
},
59-
Logger: logger,
60-
IgnoreExecError: true,
58+
Logger: logger,
6159
})
6260
require.NoError(t, err)
6361

pkg/mrunners/runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
type RunnerAction interface {
14-
Do(ctx context.Context)
14+
Do(ctx context.Context) (err error)
1515
}
1616

1717
type Runner struct {

pkg/mrunners/runner_cron.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,58 @@ type runnerCron struct {
3030
RunnerOptions
3131
}
3232

33-
func (r *runnerCron) Do(ctx context.Context) {
33+
func (r *runnerCron) Do(ctx context.Context) (err error) {
3434
r.Print("controller started")
3535
defer r.Print("controller exited")
3636

3737
if r.Unit.Immediate {
38-
if err := r.Exec.Execute(r.Unit.ExecuteOptions(r.Logger)); err != nil {
38+
if err = r.Exec.Execute(r.Unit.ExecuteOptions(r.Logger)); err != nil {
3939
r.Error("failed executing: " + err.Error())
40+
if r.Unit.Critical {
41+
return
42+
} else {
43+
err = nil
44+
}
4045
}
4146
}
4247

4348
cr := cron.New(cron.WithLogger(cron.PrintfLogger(r.Logger)))
44-
_, err := cr.AddFunc(r.Unit.Cron, func() {
49+
50+
var chErr chan error
51+
if r.Unit.Critical {
52+
chErr = make(chan error, 1)
53+
}
54+
55+
if _, err = cr.AddFunc(r.Unit.Cron, func() {
4556
r.Print("triggered")
4657
if err := r.Exec.Execute(r.Unit.ExecuteOptions(r.Logger)); err != nil {
4758
r.Error("failed executing: " + err.Error())
59+
if r.Unit.Critical {
60+
select {
61+
case chErr <- err:
62+
default:
63+
}
64+
} else {
65+
err = nil
66+
}
4867
}
49-
})
50-
51-
if err != nil {
52-
panic(err)
68+
}); err != nil {
69+
// should fail since we have checked in init
70+
return
5371
}
5472

5573
cr.Start()
5674

57-
<-ctx.Done()
75+
if r.Unit.Critical {
76+
select {
77+
case <-ctx.Done():
78+
case err = <-chErr:
79+
}
80+
} else {
81+
<-ctx.Done()
82+
}
83+
5884
<-cr.Stop().Done()
85+
86+
return
5987
}

0 commit comments

Comments
 (0)