Skip to content

Commit

Permalink
Provide option to timeout functions earlier than configured
Browse files Browse the repository at this point in the history
A header of X-Timeout can be passed to reduce the value of
exec_timeout. The consumer invoking the function is responsible
for tuning this value and making sure it has enough headroom.

The change was requested by @kevin-lindsay-1 for functions
which process batched data of varying size and could run for
24 hours or 2 minutes, but need to timeout in a timely
fashion, according to the input.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
  • Loading branch information
alexellis committed Jul 11, 2024
1 parent 3d089de commit ba8716e
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ In HTTP mode, the watchdog will append the X-Call-Id to its own HTTP log message
2024/04/25 17:29:58 GET / - 301 Moved Permanently - ContentLength: 39B (0.0037s) [079d9ff9-d7b7-4e37-b195-5ad520e6f797]
```

#### 1.5 Reducing timeouts

If a function has a timeout set via `exec_timeout` of a large value like `1h`, but you need an individual request to timeout earlier, i.e. `1m`, then you can pass in a HTTP header of `X-Timeout` with a Go duration to override the behaviour.

The value for `X-Timeout` must be equal to or shorter than the `exec_timeout` environment variable.

`X-Timeout` cannot be set when the `exec_timeout` is set to `0` or hasn't been specified.

### 2. Serializing fork (mode=serializing)

#### 2.1 Status
Expand Down
20 changes: 18 additions & 2 deletions executor/http_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,13 @@ func (f *HTTPFunctionRunner) Run(req FunctionRequest, contentLength int64, r *ht
request.Host = r.Host
copyHeaders(request.Header, &r.Header)

execTimeout := getTimeout(r, f.ExecTimeout)

var reqCtx context.Context
var cancel context.CancelFunc

if f.ExecTimeout.Nanoseconds() > 0 {
reqCtx, cancel = context.WithTimeout(r.Context(), f.ExecTimeout)
if execTimeout.Nanoseconds() > 0 {
reqCtx, cancel = context.WithTimeout(r.Context(), execTimeout)
} else {
reqCtx = r.Context()
cancel = func() {
Expand Down Expand Up @@ -203,6 +205,20 @@ func (f *HTTPFunctionRunner) Run(req FunctionRequest, contentLength int64, r *ht
return nil
}

func getTimeout(r *http.Request, defaultTimeout time.Duration) time.Duration {
execTimeout := defaultTimeout
if v := r.Header.Get("X-Timeout"); len(v) > 0 {
dur, err := time.ParseDuration(v)
if err == nil {
if dur <= defaultTimeout {
execTimeout = dur
}
}
}

return execTimeout
}

func copyHeaders(destination http.Header, source *http.Header) {
for k, v := range *source {
vClone := make([]string, len(v))
Expand Down
49 changes: 49 additions & 0 deletions executor/http_runner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package executor

import (
"net/http/httptest"
"testing"
"time"
)

func TestGetTimeout_PicksLowerOverride(t *testing.T) {
override := time.Second * 10
r := httptest.NewRequest("GET", "http://localhost:8080", nil)
r.Header.Add("X-Timeout", override.Round(time.Second).String())

defaultTimeout := time.Second * 20
got := getTimeout(r, defaultTimeout)
want := time.Second * 10

if got != want {
t.Errorf("getTimeout() got: %v, want %v", got, want)
}
}

func TestGetTimeout_CapsOverrideToDefaultValue(t *testing.T) {
override := time.Second * 21
r := httptest.NewRequest("GET", "http://localhost:8080", nil)
r.Header.Add("X-Timeout", override.Round(time.Second).String())

defaultTimeout := time.Second * 20
got := getTimeout(r, defaultTimeout)
want := time.Second * 20

if got != want {
t.Errorf("getTimeout() got: %v, want %v", got, want)
}
}

func TestGetTimeout_NoDefaultMeansNoOverride(t *testing.T) {
override := time.Second * 10
r := httptest.NewRequest("GET", "http://localhost:8080", nil)
r.Header.Add("X-Timeout", override.Round(time.Second).String())

defaultTimeout := time.Nanosecond * 0
got := getTimeout(r, defaultTimeout)
want := time.Nanosecond * 0

if got != want {
t.Errorf("getTimeout() got: %v, want %v", got, want)
}
}

0 comments on commit ba8716e

Please sign in to comment.