将sync.Pool引用用作上下文值是否安全?

I have struct that uses sync.Pool.

Is it safe to use this reference as context value?

type User struct {
    ID string
}

var userPool = sync.Pool{
    New: func() interface{} {
        return &User{}
    },
}

func getUser() *User {
    return userPool.Get().(*User)
}

func recycleUser(user *User) {
    userPool.Put(user)
}

The user struct is retrieved from pool in middleware.

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // get user from pool
        user := getUser()
        // user must be recycled 
        ctx := context.WithValue(r.Context(), "user", user)
    }
}

And recycled in handler.

func getUser(w http.ResponseWriter, r *http.Request) {
    user := r.Context().Value("user").(*User)

    // TODO: do something with user

    // put user struct back into pool
    recycleUser(user)
}

Edit:

My question is more about how context deals with pointer to my object. Does it make a copy? Is it safe to use non-primitive object with context?

There are three points to make:

Usage of sync.Pool

Your usage of pool is as intended. From golang Documentation

A Pool is safe for use by multiple goroutines simultaneously.

Retrieving users

getUser uses func (p *Pool) Get() interface{} which removes the returned item from the pool, afterwards you can do whatever you please with that value. In your case, thats a *User. Whether the associated ressource is safe to use, well, depends on your usage of those values in the rest of the program.

Recycling users

Calling recycleUser within the hander is potentially dangerous, depending on your environment.

What could possibly go wrong?

After recycleUser returned your *User back to the pool, it could be retrieved and used by a different goroutine immediately. But at the same time, *User is still stored in the context assiociated with the request. So it depends if any of the functions of your middleware use the *User from the context, too and wether they store the *User value. Or if you later add some code after the recycleUser, which uses the *User value. After the recycleUser call, all those usages probably operate on the wrong user, which is already used by some different request.

How to solve those issues

  1. coding convention

    • if you retrieve the user in middleware functions, don't store it
    • if you retrieve the user in middleware functions, don't use it after the call to the next handler in the chain.
    • don't call recycleUser in middleware functions
    • retrieve the user in the leaf handler (last handler in the chain, doesn't calls any further handlers propagating the context and thus the *User) and use defer recycleUser(user) to put back the *User in the pool and make it impossible that later added code uses *User after the call to recycleUser(user).
    • in the leaf handler, don't use *User in code which is called by some defer usage in the leaf handler, other than recycleUser.
  2. ensuring the coding convention by technical means. My only idea here would be to place the *User inside some struct and only use that struct for one request and mark it as empty, once *User is put back into the pool. Make all access methods of that struct check for emptyness and panic/log/whatever, if an empty struct is accessed. Place a pointer to that struct in the context. But that is probably pointless, as you now allocate that struct for each request, instead of a User, which probably was, what you tried to avoid.

A minor remark

You probably meant your middleware function to read like:

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // get user from pool
        user := getUser()
        // user must be recycled 
        ctx := context.WithValue(r.Context(), "user", user)
        // probably do something more
        next(w, r.WithContext(ctx))
    }
}