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 pre
is not followed byfirst post
, same assecond
third
. - the order of
post
s is reversed. theendpoint.Chain
reverse the execution of a list ofannotate
returned values butannotate
methods are evaluated first right? Not to say, thepre
s 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.