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:
Your usage of pool is as intended. From golang Documentation
A Pool is safe for use by multiple goroutines simultaneously.
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.
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.
coding convention
recycleUser
in middleware functions*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)
.*User
in code which is called by some defer
usage in the leaf handler, other than recycleUser
.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.
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))
}
}