I am learning the golang source code and get stuck in the defer function execution order. I have two files: one defines the behavior of an endpoint and another one for the test. I remove some code unrelated to my question to reduce the lines to read. The endpoint definition file
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
// Chain is a helper function for composing middlewares. Requests will
// traverse them in the order they're declared. That is, the first middleware
// is treated as the outermost middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}
The test file contains the printed steps.
func ExampleChain() {
e := endpoint.Chain(
annotate("first"),
annotate("second"),
annotate("third"),
)(myEndpoint)
if _, err := e(ctx, req); err != nil {
panic(err)
}
// Output:
// first pre
// second pre
// third pre
// my endpoint!
// third post
// second post
// first post
}
var (
ctx = context.Background()
req = struct{}{}
)
func annotate(s string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println(s, "pre")
defer fmt.Println(s, "post")
return next(ctx, request)
}
}
}
func myEndpoint(context.Context, interface{}) (interface{}, error) {
fmt.Println("my endpoint!")
return struct{}{}, nil
}
To my understanding, the three annotate methods should be executed first, followed by the endpoint.Chain method and myEndpoint should be executed in the end. Also since the pre is printed first and when the funtion returns "post" should follow according to the defer explanation in the go doc:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
So what I expect to see is
// Output:
// first pre
// first post
// second pre
// second post
// third pre
// third post
// my endpoint!
In short, my questions are:
- why
first preis not followed byfirst post, same assecondthird. - the order of
posts is reversed. theendpoint.Chainreverse the execution of a list ofannotatereturned values butannotatemethods are evaluated first right? Not to say, thepres get printed which means the inner funtions are executed first
CodePudding user response:
A deferred function runs as the last thing in the function, after the return statement, so the annotate function will first run next, and only after that returns the deferred function will run. Based on your code, the order it should print is:
first pre
second pre
third pre
my endpoint
third post
second post
first post
CodePudding user response:
Here is your example turned into something that runs on the Go playground.
Note that if you call defer more than once in a given function, each deferred call runs in LIFO order. So if you want to use defer to make sure your post gets called first, and then the next operates, consider replacing:
defer fmt.Println(s, "post")
next(ctx, request)
with:
defer next(ctx, request)
defer fmt.Println(s, "post)
Of course, in your case you want to return what next returns, which creates a small problem. To work around this in real cases you need a small function and some named return values:
defer func() { i, e = next(ctx, request) }()
where i and e are the named return values.
Here is that same code turned into a new example in which the deferred calls occur in the desired order. In this case, the example is rather silly, as nothing panics and there are no "dangerous steps" in between, so all we really need is to do the two fmt.Println calls sequentially, without using defer. But if we could panic between the fmt.Println(s, "pre") and the post section, then this might make sense.
