apache / apisix-go-plugin-runner

Go Plugin Runner for APISIX
https://apisix.apache.org/
Apache License 2.0
169 stars 69 forks source link

request help: Introduce context to run plugin #34

Closed spacewander closed 2 years ago

spacewander commented 3 years ago

Issue description

Currently, we have an implicit 60s timeout when running the Go plugin. The common way in Go is to use a Context that will time out after the 60s.

Therefore, we should find a way to introduce Context into plugin runner.

If you are interested in this issue, please discuss your design here before submitting any code. Thanks.

Environment

Belyenochi commented 2 years ago

pls assigned to me.

Belyenochi commented 2 years ago

Environment

My idea is the following in the internal/server/server.go file

// sample code
go func() {
        for {
            conn, err := l.Accept()

            select {
            case <-done:
                // don't report the "use of closed network connection" error when the server
                // is exiting.
                return
            default:
            }

            if err != nil {
                log.Errorf("accept: %s", err)
                continue
            }

                         // Modified content
            go func() {
                ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
                defer cancel()
                isExit := make(chan bool)
                go handleConn(conn, isExit)
                select {
                case <-isExit:
                    fmt.Println("exit")
                case <-ctx.Done():
                    builder := util.GetBuilder()
                    hrc.RespStart(builder)
                    res := hrc.RespEnd(builder)
                    builder.Finish(res)
                    out := builder.FinishedBytes()
                    n, err := conn.Write(out)
                    if err != nil {
                        util.WriteErr(n, err)
                    }
                }
            }()
        }
    }()

    sig := <-quit
    log.Warnf("server receive %s and exit", sig.String())
    close(done)
}

But there are two problems with this scheme First, there is no way to control APISIX's return to the user at this time (maybe I should take a look at ext-plugin-proto) Second, there is no fine-grained control over the operation of a single plugin, and the serialization between plugins may cause some plugins to be starved all the time (should we allow parallelism between pugins)

中文版本: 代码如上 这个方案存在两个问题 第一没法控制APISIX此时对于用户的返回(或许我应该去看看ext-plugin-proto) 第二没法细粒度的控制单个plugin运行,plugin之间依旧串行可能会引起部分plugin一直饥饿(我们是否应该允许plugin之间并行)

是否能在Slack里面进行更实时的探讨或者其他实时通讯方式~

spacewander commented 2 years ago

Thanks for your design. My original thought is simpler: we can add a method in the Request to return a Context. It works like the one from the standard library: https://pkg.go.dev/net/http#Request.Context.

People can fetch it only when they need the Context. And the timeout of Context is slightly smaller than 60s so people can still break the execution with a custom response before the actual timeout.

There is a tricky place: the timeout of Nginx is per-operation, so when we communicate with Nginx again in https://github.com/apache/apisix-go-plugin-runner/blob/6bb1c4bd98ec374638af7ed02931e0efb2604044/internal/http/request.go#L326 the timer will be reset. Maybe we can handle this in a separate PR.

Belyenochi commented 2 years ago

Thanks for your design. My original thought is simpler: we can add a method in the Request to return a Context. It works like the one from the standard library: https://pkg.go.dev/net/http#Request.Context.

People can fetch it only when they need the Context. And the timeout of Context is slightly smaller than 60s so people can still break the execution with a custom response before the actual timeout.

There is a tricky place: the timeout of Nginx is per-operation, so when we communicate with Nginx again in

https://github.com/apache/apisix-go-plugin-runner/blob/6bb1c4bd98ec374638af7ed02931e0efb2604044/internal/http/request.go#L326

the timer will be reset. Maybe we can handle this in a separate PR.

I will submit a PR asap

Belyenochi commented 2 years ago

Thanks for your design. My original thought is simpler: we can add a method in the Request to return a Context. It works like the one from the standard library: https://pkg.go.dev/net/http#Request.Context.

People can fetch it only when they need the Context. And the timeout of Context is slightly smaller than 60s so people can still break the execution with a custom response before the actual timeout.

There is a tricky place: the timeout of Nginx is per-operation, so when we communicate with Nginx again in

https://github.com/apache/apisix-go-plugin-runner/blob/6bb1c4bd98ec374638af7ed02931e0efb2604044/internal/http/request.go#L326

the timer will be reset. Maybe we can handle this in a separate PR.

There is a doubt, although we have added a timeout context to the Request, but if the plugin written by the user does not handle this context, it will still cause a timeout

spacewander commented 2 years ago

Yes. But at least it won't break existing code. This is also the way used in the std lib.

Belyenochi commented 2 years ago

Yes. But at least it won't break existing code. This is also the way used in the std lib.

Thanks for your patience, and I don't understand what's wrong with resetting the timer (for example, the client timeout is no longer controllable)?

spacewander commented 2 years ago

what's wrong with resetting the timer (for example, the client timeout is no longer controllable)?

What do you mean by saying resetting the timer?

Belyenochi commented 2 years ago

the timer will be reset

Sorry, I didn't make it clear, I don't understand whether the timer on the Nginx side can be reset by the client

spacewander commented 2 years ago

The socket timeout of Nginx is counting per operation, not on the whole progress.