Skip to content

Commit b88f5a2

Browse files
committed
Update README.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com> Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
1 parent b8d18d2 commit b88f5a2

File tree

8 files changed

+210
-56
lines changed

8 files changed

+210
-56
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# of-watchdog
22

3-
Reverse proxy for HTTP microservices and STDIO
3+
Reverse proxy/middleware for functions using STDIO/HTTP
44

55
[![Go Report Card](https://goreportcard.com/badge/github.com/openfaas/of-watchdog)](https://goreportcard.com/report/github.com/openfaas/of-watchdog) [![build](https://github.com/openfaas/of-watchdog/actions/workflows/build.yaml/badge.svg)](https://github.com/openfaas/of-watchdog/actions/workflows/build.yaml)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -205,6 +205,7 @@ Environmental variables:
205205
| `log_call_id` | In HTTP mode, when printing a response code, content-length and timing, include the X-Call-Id header at the end of the line in brackets i.e. `[079d9ff9-d7b7-4e37-b195-5ad520e6f797]` or `[none]` when it's empty. Default: `false` |
206206
| `max_inflight` | Limit the maximum number of requests in flight, and return a HTTP status 429 when exceeded |
207207
| `mode` | The mode which of-watchdog operates in, Default `streaming` [see doc](#3-streaming-fork-modestreaming---default). Options are [http](#1-http-modehttp), [serialising fork](#2-serializing-fork-modeserializing), [streaming fork](#3-streaming-fork-modestreaming---default), [static](#4-static-modestatic) |
208+
| `one_shot` | When set to `true`, accept the first genuine invoke request, then immediately begin graceful shutdown and reject subsequent invoke requests. Readiness and health endpoints do not trigger this mode. |
208209
| `port` | Specify an alternative TCP port for testing. Default: `8080` |
209210
| `prefix_logs` | When set to `true` the watchdog will add a prefix of "Date Time" + "stderr/stdout" to every line read from the function process. Default `true` |
210211
| `read_timeout` | HTTP timeout for reading the payload from the client caller (in seconds) |
@@ -221,4 +222,3 @@ Unsupported options from the [Classic Watchdog](https://github.com/openfaas/clas
221222
| `write_debug` | In the classic watchdog, this prints the response body out to the console |
222223
| `read_debug` | In the classic watchdog, this prints the request body out to the console |
223224
| `combined_output` | In the classic watchdog, this returns STDOUT and STDERR in the function's HTTP response, when off it only returns STDOUT and prints STDERR to the logs of the watchdog |
224-

config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ type WatchdogConfig struct {
6969
// HTTP mode.
7070
LogCallId bool
7171

72+
// OneShot marks the watchdog as unhealthy and begins graceful shutdown
73+
// when the first genuine invoke request starts.
74+
OneShot bool
75+
7276
// Handler is the HTTP handler to use in "inproc" mode
7377
Handler http.HandlerFunc
7478
}
@@ -178,6 +182,7 @@ func New(env []string) (WatchdogConfig, error) {
178182
LogBufferSize: logBufferSize,
179183
ReadyEndpoint: envMap["ready_path"],
180184
LogCallId: logCallId,
185+
OneShot: getBool(envMap, "one_shot"),
181186
}
182187

183188
if val := envMap["mode"]; len(val) > 0 {

config/config_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,46 @@ func Test_NonParsableString_parseIntOrDurationValue(t *testing.T) {
389389
t.Error(fmt.Sprintf("want: %q got: %q", want, got))
390390
}
391391
}
392+
393+
func TestNewParsesOneShot(t *testing.T) {
394+
cfg, err := New([]string{
395+
"fprocess=/bin/cat",
396+
"mode=streaming",
397+
"one_shot=true",
398+
})
399+
if err != nil {
400+
t.Fatalf("expected config to parse, got error: %v", err)
401+
}
402+
403+
if !cfg.OneShot {
404+
t.Fatalf("expected OneShot to be true")
405+
}
406+
}
407+
408+
func TestNewParsesOneShotNumericValues(t *testing.T) {
409+
cfg, err := New([]string{
410+
"fprocess=/bin/cat",
411+
"mode=streaming",
412+
"one_shot=1",
413+
})
414+
if err != nil {
415+
t.Fatalf("expected config to parse, got error: %v", err)
416+
}
417+
418+
if !cfg.OneShot {
419+
t.Fatalf("expected one_shot=1 to enable OneShot")
420+
}
421+
422+
cfg, err = New([]string{
423+
"fprocess=/bin/cat",
424+
"mode=streaming",
425+
"one_shot=0",
426+
})
427+
if err != nil {
428+
t.Fatalf("expected config to parse, got error: %v", err)
429+
}
430+
431+
if cfg.OneShot {
432+
t.Fatalf("expected one_shot=0 to disable OneShot")
433+
}
434+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/beorn7/perks v1.0.1 // indirect
1414
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1515
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
16-
github.com/gorilla/mux v1.8.1 // indirect
16+
github.com/kr/text v0.2.0 // indirect
1717
github.com/kylelemons/godebug v1.1.0 // indirect
1818
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
1919
github.com/prometheus/client_model v0.6.2 // indirect

go.sum

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
22
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
33
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
44
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
56
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
67
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
78
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -10,18 +11,18 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
1011
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
1112
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
1213
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
13-
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
14-
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
1514
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
1615
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
1716
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1817
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1918
github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0 h1:my2ucqBZmv+cWHIhZNSIYKzgN8EBGyHdC7zD5sASRAg=
2019
github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
21-
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
22-
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
2320
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
2421
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
22+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
23+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
24+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
25+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
2526
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
2627
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
2728
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -32,76 +33,42 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
3233
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
3334
github.com/openfaas/faas-middleware v1.2.5 h1:RXPsuXw1PsxPsQ0MJz0ud3mU6X+Rg8v50ZAPxapVJ1g=
3435
github.com/openfaas/faas-middleware v1.2.5/go.mod h1:YuGnJ7wVW/dJEzZCltHYKGGHwfelQMPWt205m6PNfVE=
35-
github.com/openfaas/faas-provider v0.25.8 h1:3W1ZyhUvpqTOiNQoV7jzdZhDJeJJlJelosAtjFzMNyw=
36-
github.com/openfaas/faas-provider v0.25.8/go.mod h1:rMXbj+AYVpn82UoHIOgWHiDeV118t0bSxyoC9d00jpc=
37-
github.com/openfaas/faas-provider v0.25.9 h1:Y4m5fGm3pu6bUdIm2pNhXC0JWdkoWcVkfuGI3tUYDMQ=
38-
github.com/openfaas/faas-provider v0.25.9/go.mod h1:rMXbj+AYVpn82UoHIOgWHiDeV118t0bSxyoC9d00jpc=
3936
github.com/openfaas/faas-provider v0.25.10 h1:fCf2i1vCx3UL8o9gjYPt7CcX8Kko2dc6xme7ITIa8uY=
4037
github.com/openfaas/faas-provider v0.25.10/go.mod h1:rMXbj+AYVpn82UoHIOgWHiDeV118t0bSxyoC9d00jpc=
4138
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4239
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
43-
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
44-
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
4540
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
4641
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
4742
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
4843
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
49-
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
50-
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
51-
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
52-
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
53-
github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
54-
github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
5544
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
5645
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
57-
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
58-
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
59-
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
60-
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
6146
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
6247
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
6348
github.com/rakutentech/jwk-go v1.2.0 h1:vNJwedPkRR+32V5WGNj0JP4COes93BGERvzQLBjLy4c=
6449
github.com/rakutentech/jwk-go v1.2.0/go.mod h1:pI0bYVntqaJ27RCpaC75MTUacheW0Rk4+8XzWWe1OWM=
65-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
66-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
50+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
51+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
6752
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
53+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
6854
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
6955
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
7056
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
7157
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
72-
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
73-
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
74-
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
75-
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
76-
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
77-
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
7858
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
7959
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
80-
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
81-
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
82-
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
83-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
84-
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
85-
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
86-
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
87-
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
88-
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
89-
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
60+
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
61+
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
9062
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
9163
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
92-
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
93-
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
94-
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
64+
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
65+
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
9566
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
9667
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
97-
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
98-
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
99-
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
100-
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
101-
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
102-
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
10368
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
10469
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
10570
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
71+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
72+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
10673
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10774
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/watchdog.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package pkg
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"log"
@@ -12,6 +13,7 @@ import (
1213
"os/signal"
1314
"path/filepath"
1415
"strings"
16+
"sync"
1517
"sync/atomic"
1618
"syscall"
1719
"time"
@@ -50,8 +52,25 @@ func (w *Watchdog) Start(ctx context.Context) error {
5052
baseFunctionHandler := buildRequestHandler(w.config, w.config.PrefixLogs)
5153
requestHandler := baseFunctionHandler
5254

55+
drainCh := make(chan string, 1)
56+
var drainOnce sync.Once
57+
startDrain := func(reason string) {
58+
drainOnce.Do(func() {
59+
if err := markUnhealthy(); err != nil {
60+
log.Printf("Unable to mark server as unhealthy: %s\n", err.Error())
61+
}
62+
63+
log.Printf("Scheduling graceful shutdown: %s\n", reason)
64+
drainCh <- reason
65+
})
66+
}
67+
68+
if w.config.OneShot {
69+
requestHandler = makeOneShotHandler(requestHandler, w.config.ReadyEndpoint, startDrain)
70+
}
71+
5372
if w.config.JWTAuthentication {
54-
handler, err := makeJWTAuthHandler(w.config, baseFunctionHandler)
73+
handler, err := makeJWTAuthHandler(w.config, requestHandler)
5574
if err != nil {
5675
return fmt.Errorf("error creating JWTAuthMiddleware: %w", err)
5776
}
@@ -111,6 +130,7 @@ func (w *Watchdog) Start(ctx context.Context) error {
111130
w.config.HealthcheckInterval,
112131
w.config.HTTPWriteTimeout,
113132
w.config.SuppressLock,
133+
drainCh,
114134
&httpMetrics)
115135

116136
return nil
@@ -122,32 +142,46 @@ func markUnhealthy() error {
122142
path := filepath.Join(os.TempDir(), ".lock")
123143
log.Printf("Removing lock-file : %s\n", path)
124144
removeErr := os.Remove(path)
145+
if errors.Is(removeErr, os.ErrNotExist) {
146+
return nil
147+
}
125148
return removeErr
126149
}
127150

128-
func listenUntilShutdown(s *http.Server, shutdownCtx context.Context, healthcheckInterval time.Duration, writeTimeout time.Duration, suppressLock bool, httpMetrics *metrics.Http) error {
151+
func listenUntilShutdown(s *http.Server, shutdownCtx context.Context, healthcheckInterval time.Duration, writeTimeout time.Duration, suppressLock bool, drain <-chan string, httpMetrics *metrics.Http) error {
129152

130153
idleConnsClosed := make(chan struct{})
131154
go func() {
132155
sig := make(chan os.Signal, 1)
133156
signal.Notify(sig, syscall.SIGTERM)
134157

135158
reason := ""
159+
drainDelay := healthcheckInterval
136160

137161
select {
138162
case <-sig:
139163
reason = "SIGTERM"
140164
case <-shutdownCtx.Done():
141165
reason = "Context cancelled"
166+
case reason = <-drain:
167+
drainDelay = 0
142168
}
143169

144-
log.Printf("%s: no new connections in %s\n", reason, healthcheckInterval.String())
170+
if drainDelay > 0 {
171+
log.Printf("%s: no new connections in %s\n", reason, drainDelay.String())
172+
} else {
173+
log.Printf("%s: no new connections allowed immediately\n", reason)
174+
}
145175

146176
if err := markUnhealthy(); err != nil {
147177
log.Printf("Unable to mark server as unhealthy: %s\n", err.Error())
148178
}
149179

150-
<-time.Tick(healthcheckInterval)
180+
if drainDelay > 0 {
181+
timer := time.NewTimer(drainDelay)
182+
defer timer.Stop()
183+
<-timer.C
184+
}
151185

152186
connections := int64(testutil.ToFloat64(httpMetrics.InFlight))
153187
log.Printf("No new connections allowed, draining: %d requests\n", connections)
@@ -193,6 +227,34 @@ func listenUntilShutdown(s *http.Server, shutdownCtx context.Context, healthchec
193227
return nil
194228
}
195229

230+
func makeOneShotHandler(next http.Handler, readyEndpoint string, startDrain func(string)) http.Handler {
231+
var served int32
232+
233+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
234+
if r.URL != nil {
235+
switch r.URL.Path {
236+
case "/_/health", "/_/ready":
237+
next.ServeHTTP(w, r)
238+
return
239+
case readyEndpoint:
240+
if readyEndpoint != "" {
241+
next.ServeHTTP(w, r)
242+
return
243+
}
244+
}
245+
}
246+
247+
if !atomic.CompareAndSwapInt32(&served, 0, 1) {
248+
w.WriteHeader(http.StatusServiceUnavailable)
249+
w.Write([]byte("watchdog is draining after serving a request"))
250+
return
251+
}
252+
253+
startDrain("one_shot")
254+
next.ServeHTTP(w, r)
255+
})
256+
}
257+
196258
func buildRequestHandler(cfg config.WatchdogConfig, prefixLogs bool) http.Handler {
197259
var requestHandler http.HandlerFunc
198260

0 commit comments

Comments
 (0)