openfaas / golang-http-template

Golang templates for OpenFaaS using HTTP extensions
https://www.openfaas.com/
MIT License
106 stars 57 forks source link

golang-http does not allow graceful request cancelling #40

Closed LucasRoesler closed 4 years ago

LucasRoesler commented 4 years ago

Because the go-function-sdk does not propagate the original request context in the Request object, the handler can not correctly respond to the request being cancelled by the user. In the Go stdlib this information is generally conveyed by the checking if request.Context() is done.

If the handler has an expensive operation that should be canceled and reverted, e.g. a DB transaction, this would be impossible because the handler can not check for the early cancellation.

We should be able to see this by creating a handler that prints/counts to a large number, issuing a request and then immediately cancelling. The handler should continue to count even after the user aborts the request.

For example, using this

# stack.yml
version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:808

configuration:
  templates:
    - name: golang-http
      source: https://github.com/openfaas-incubator/golang-http-template

functions:
  counter:
    lang: golang-http
    handler: ./counter
    image: counter:latest
# counter/handler.go
package function

import (
    "fmt"
    "net/http"

    handler "github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
    var err error

    for i := 0; i < 1000; i++ {
        fmt.Printf("count %d\n", i)
    }

    message := fmt.Sprintf("Hello world, input was: %s", string(req.Body))
    return handler.Response{
        Body:       []byte(message),
        StatusCode: http.StatusOK,
    }, err
}

Build and run the function

faas-cli build -f stack.yml
docker run --rm -p 8080:8080 counter:latest 

Then in a new terminal

curl localhost:8080 -d "anything"
<ctrl>-c

You will see the function start and then continue even after you ctrl-c the curl request. This is because the handler has not even attempted to check for the abort. But it is not currently possible to do this because the context is not available.

alexellis commented 4 years ago

Thanks for this example, please can you now show us how it would work differently with the cancellation in place?

LucasRoesler commented 4 years ago

I was just about to add that, I would expect this function to stop as soon as the ctrl-c happens

package function

import (
    "fmt"
    "net/http"

    handler "github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
    var err error

    for i := 0; i < 10000; i++ {
        if req.Context().Err() != nil  {
            return handler.Response{}, fmt.Errorf("request cancelled")
        }
        fmt.Printf("count %d\n", i)
    }

    message := fmt.Sprintf("Hello world, input was: %s", string(req.Body))
    return handler.Response{
        Body:       []byte(message),
        StatusCode: http.StatusOK,
    }, err
}
alexellis commented 4 years ago

This would make a good addition to the README.md as an example:

I.e. "How to cancel a long running operation" or similar

alexellis commented 4 years ago

Is this issue resloved now?

LucasRoesler commented 4 years ago

I will PR the updated docs against this, but i think it is fixed once the of-watchdog is updated in the templates as well