forked from travis-ci/worker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
step_run_script.go
182 lines (148 loc) · 6.06 KB
/
step_run_script.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
package worker
import (
"fmt"
"time"
gocontext "context"
"github.com/mitchellh/multistep"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/travis-ci/worker/backend"
"github.com/travis-ci/worker/context"
"go.opencensus.io/trace"
)
var MaxLogLengthExceeded = errors.New("maximum log length exceeded")
var LogWriterTimeout = errors.New("log writer timeout")
type runScriptReturn struct {
result *backend.RunResult
err error
}
type stepRunScript struct {
logTimeout time.Duration
hardTimeout time.Duration
skipShutdownOnLogTimeout bool
}
func (s *stepRunScript) Run(state multistep.StateBag) multistep.StepAction {
ctx := state.Get("ctx").(gocontext.Context)
buildJob := state.Get("buildJob").(Job)
instance := state.Get("instance").(backend.Instance)
logWriter := state.Get("logWriter").(LogWriter)
cancelChan := state.Get("cancelChan").(<-chan CancellationCommand)
defer context.TimeSince(ctx, "step_run_script_run", time.Now())
ctx, span := trace.StartSpan(ctx, "RunScript.Run")
defer span.End()
preTimeoutCtx := ctx
logger := context.LoggerFromContext(ctx).WithField("self", "step_run_script")
ctx, cancel := gocontext.WithTimeout(ctx, s.hardTimeout)
logWriter.SetCancelFunc(cancel)
defer cancel()
logger.Info("running script")
defer logger.Info("finished script")
resultChan := make(chan runScriptReturn, 1)
go func() {
result, err := instance.RunScript(ctx, logWriter)
resultChan <- runScriptReturn{
result: result,
err: err,
}
}()
select {
case r := <-resultChan:
// We need to check for this since it's possible that the RunScript
// implementation returns with the error too quickly for the ctx.Done()
// case branch below to catch it.
if errors.Cause(r.err) == gocontext.DeadlineExceeded {
state.Put("err", r.err)
logger.Info("hard timeout exceeded, terminating")
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum time limit for jobs, and has been terminated.\n\n")
// Continue to the download trace step
return multistep.ActionContinue
}
if logWriter.MaxLengthReached() {
state.Put("err", MaxLogLengthExceeded)
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum log length, and has been terminated.\n\n")
// Continue to the download trace step
return multistep.ActionContinue
}
if r.err != nil {
state.Put("err", r.err)
span.SetStatus(trace.Status{
Code: trace.StatusCodeUnavailable,
Message: r.err.Error(),
})
if !r.result.Completed {
logger.WithFields(logrus.Fields{
"err": r.err,
"completed": r.result.Completed,
}).Error("couldn't run script, attempting requeue")
context.CaptureError(ctx, r.err)
err := buildJob.Requeue(preTimeoutCtx)
if err != nil {
logger.WithField("err", err).Error("couldn't requeue job")
}
} else {
logger.WithField("err", r.err).WithField("completed", r.result.Completed).Error("couldn't run script")
err := buildJob.Finish(preTimeoutCtx, FinishStateErrored)
if err != nil {
logger.WithField("err", err).Error("couldn't mark job errored")
}
}
return multistep.ActionHalt
}
state.Put("scriptResult", r.result)
return multistep.ActionContinue
case <-ctx.Done():
state.Put("err", ctx.Err())
span.SetStatus(trace.Status{
Code: trace.StatusCodeUnavailable,
Message: ctx.Err().Error(),
})
if ctx.Err() == gocontext.DeadlineExceeded {
logger.Info("hard timeout exceeded, terminating")
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum time limit for jobs, and has been terminated.\n\n")
// Continue to the download trace step
return multistep.ActionContinue
}
if logWriter.MaxLengthReached() {
state.Put("err", MaxLogLengthExceeded)
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum log length, and has been terminated.\n\n")
// Continue to the download trace step
return multistep.ActionContinue
}
logger.Info("context was cancelled, stopping job")
return multistep.ActionHalt
case cancelCommand := <-cancelChan:
state.Put("err", JobCancelledError)
span.SetStatus(trace.Status{
Code: trace.StatusCodeUnavailable,
Message: JobCancelledError.Error(),
})
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateCancelled, fmt.Sprintf("\n\nDone: Job Cancelled\n\n%s", cancelCommand.Reason))
return multistep.ActionHalt
case <-logWriter.Timeout():
state.Put("err", LogWriterTimeout)
span.SetStatus(trace.Status{
Code: trace.StatusCodeUnavailable,
Message: LogWriterTimeout.Error(),
})
s.writeLogAndFinishWithState(preTimeoutCtx, ctx, logWriter, buildJob, FinishStateErrored, fmt.Sprintf("\n\nNo output has been received in the last %v, this potentially indicates a stalled build or something wrong with the build itself.\nCheck the details on how to adjust your build configuration on: https://docs.travis-ci.com/user/common-build-problems/#build-times-out-because-no-output-was-received\n\nThe build has been terminated\n\n", s.logTimeout))
if s.skipShutdownOnLogTimeout {
state.Put("skipShutdown", true)
}
// Continue to the download trace step
return multistep.ActionContinue
}
}
func (s *stepRunScript) writeLogAndFinishWithState(preTimeoutCtx, ctx gocontext.Context, logWriter LogWriter, buildJob Job, state FinishState, logMessage string) {
ctx, span := trace.StartSpan(ctx, "WriteLogAndFinishWithState.RunScript")
defer span.End()
logger := context.LoggerFromContext(ctx).WithField("self", "step_run_script")
_, err := logWriter.WriteAndClose([]byte(logMessage))
if err != nil {
logger.WithField("err", err).Error("couldn't write final log message")
}
err = buildJob.Finish(preTimeoutCtx, state)
if err != nil {
logger.WithField("err", err).WithField("state", state).Error("couldn't update job state")
}
}
func (s *stepRunScript) Cleanup(state multistep.StateBag) {}