Home > OS >  Defer function execution order
Defer function execution order

Time:10-31

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:

  1. why first pre is not followed by first post, same as second third.
  2. the order of posts is reversed. the endpoint.Chain reverse the execution of a list of annotate returned values but annotate methods are evaluated first right? Not to say, the pres 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.

  •  Tags:  
  • go
  • Related