Skip to content

Commit 91992b7

Browse files
committed
Add timeout setting to Steps
This feature allows a Task author to specify a timeout for a Step. An example use case is when a Task author would like to execute a Step for setting up an execution environment. One may expect this Step to execute within a few seconds. If the execution time takes longer than expected one may rather want to fail fast than wait for the TaskRun timeout to abort the TaskRun (example by @imjasonh). Closes tektoncd#1690
1 parent f0954c7 commit 91992b7

19 files changed

+282
-152
lines changed

cmd/entrypoint/main.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"flag"
2122
"log"
2223
"os"
@@ -40,6 +41,7 @@ var (
4041
terminationPath = flag.String("termination_path", "/tekton/termination", "If specified, file to write upon termination")
4142
results = flag.String("results", "", "If specified, list of file names that might contain task results")
4243
waitPollingInterval = time.Second
44+
timeout = flag.String("timeout", "", "If specified, sets timeout for step")
4345
)
4446

4547
func main() {
@@ -78,7 +80,16 @@ func main() {
7880
log.Printf("non-fatal error copying credentials: %q", err)
7981
}
8082

81-
if err := e.Go(); err != nil {
83+
// Add timeout to context if a non-zero timeout is specified for a step
84+
ctx := context.Background()
85+
timeoutContext, _ := time.ParseDuration(*timeout)
86+
var cancel context.CancelFunc
87+
if timeoutContext != time.Duration(0) {
88+
ctx, cancel = context.WithTimeout(ctx, timeoutContext)
89+
defer cancel()
90+
}
91+
92+
if err := e.Go(ctx); err != nil {
8293
switch t := err.(type) {
8394
case skipError:
8495
log.Print("Skipping step because a previous step failed")

cmd/entrypoint/runner.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"os"
56
"os/exec"
67
"os/signal"
@@ -19,7 +20,7 @@ type realRunner struct {
1920

2021
var _ entrypoint.Runner = (*realRunner)(nil)
2122

22-
func (rr *realRunner) Run(args ...string) error {
23+
func (rr *realRunner) Run(ctx context.Context, args ...string) error {
2324
if len(args) == 0 {
2425
return nil
2526
}
@@ -33,7 +34,7 @@ func (rr *realRunner) Run(args ...string) error {
3334
signal.Notify(rr.signals)
3435
defer signal.Reset()
3536

36-
cmd := exec.Command(name, args...)
37+
cmd := exec.CommandContext(ctx, name, args...)
3738
cmd.Stdout = os.Stdout
3839
cmd.Stderr = os.Stderr
3940
// dedicated PID group used to forward signals to

cmd/entrypoint/runner_test.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package main
22

33
import (
4+
"context"
45
"os"
56
"syscall"
67
"testing"
8+
"time"
79
)
810

911
// TestRealRunnerSignalForwarding will artificially put an interrupt signal (SIGINT) in the rr.signals chan.
@@ -14,9 +16,23 @@ func TestRealRunnerSignalForwarding(t *testing.T) {
1416
rr := realRunner{}
1517
rr.signals = make(chan os.Signal, 1)
1618
rr.signals <- syscall.SIGINT
17-
if err := rr.Run("sleep", "3600"); err.Error() == "signal: interrupt" {
19+
if err := rr.Run(context.Background(), "sleep", "3600"); err.Error() == "signal: interrupt" {
1820
t.Logf("SIGINT forwarded to Entrypoint")
1921
} else {
2022
t.Fatalf("Unexpected error received: %v", err)
2123
}
2224
}
25+
26+
// TestRealRunnerTimeout tests whether cmd is killed after time.Second even though it's supposed to sleep for an hour.
27+
func TestRealRunnerTimeout(t *testing.T) {
28+
rr := realRunner{}
29+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
30+
defer cancel()
31+
// For seconds and higher time units, err.Error() returns "signal: killed".
32+
// For microseconds and lower time units, err.Error() returns "context deadline exceeded".
33+
if err := rr.Run(ctx, "sleep", "3600"); err.Error() == "signal: killed" || err.Error() == "context deadline exceeded" {
34+
t.Logf("Timeout observed")
35+
} else {
36+
t.Fatalf("Unexpected error received: %v", err)
37+
}
38+
}

docs/tasks.md

+17
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,24 @@ steps:
241241
#!/usr/bin/env bash
242242
/bin/my-binary
243243
```
244+
#### Specifying a timeout
244245

246+
A step can specify a `timeout` field. If the `Step` execution time exceeds the specified timeout, the `Step` and consequently the entire `TaskRun` is canceled.
247+
An accompanying error message is output under `status.conditions.message`.
248+
249+
The format for a timeout is a duration string as specified in the [Go time package](https://golang.org/src/time/format.go?s=40541:40587#L1364) (e.g. 1s or 1ms).
250+
251+
The example `Step` below is supposed to sleep for 60 seconds but will be canceled by the specified 5 second timeout.
252+
```yaml
253+
steps:
254+
- name: willTimeout
255+
image: ubuntu
256+
script: |
257+
#!/usr/bin/env bash
258+
echo "I am supposed to sleep for 60 seconds!"
259+
sleep 60
260+
timeout: 5s
261+
```
245262
### Specifying `Parameters`
246263

247264
You can specify parameters, such as compilation flags or artifact names, that you want to supply to the `Task` at execution time.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
kind: TaskRun
2+
apiVersion: tekton.dev/v1beta1
3+
metadata:
4+
generateName: timeout-test-
5+
spec:
6+
taskSpec:
7+
steps:
8+
- name: wait
9+
image: ubuntu
10+
script: |
11+
#!/usr/bin/env bash
12+
echo "I am supposed to sleep for 60 seconds!"
13+
sleep 60
14+
timeout: 5s

go.mod

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ module github.com/tektoncd/pipeline
33
go 1.13
44

55
require (
6+
cloud.google.com/go/storage v1.8.0
67
contrib.go.opencensus.io/exporter/stackdriver v0.13.1 // indirect
78
github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39
89
github.com/cloudevents/sdk-go/v2 v2.1.0
9-
github.com/docker/cli v0.0.0-20200210162036-a4bedce16568 // indirect
10+
github.com/docker/cli v0.0.0-20200303162255-7d407207c304 // indirect
1011
github.com/ghodss/yaml v1.0.0
1112
github.com/google/go-cmp v0.4.1
1213
github.com/google/go-containerregistry v0.1.1
@@ -18,12 +19,16 @@ require (
1819
github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e // indirect
1920
github.com/mitchellh/go-homedir v1.1.0
2021
github.com/pkg/errors v0.9.1
21-
github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887
22+
github.com/tektoncd/plumbing v0.0.0-20200217163359-cd0db6e567d2
2223
go.opencensus.io v0.22.4
2324
go.uber.org/zap v1.15.0
2425
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
2526
golang.org/x/text v0.3.3 // indirect
27+
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6 // indirect
2628
gomodules.xyz/jsonpatch/v2 v2.1.0
29+
google.golang.org/api v0.25.0
30+
gopkg.in/yaml.v2 v2.3.0 // indirect
31+
gotest.tools/v3 v3.0.2 // indirect
2732
k8s.io/api v0.17.6
2833
k8s.io/apimachinery v0.17.6
2934
k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible

0 commit comments

Comments
 (0)