First, let's look at the code:
func AsyncAdd(run func() error) {
//TODO: Put it into an asynchronous coroutine pool
go run()
}
func GetInstance(ctx context.Context,id uint64) (string, error) {
data,err := GetFromRedis(ctx,id)
if err != nil && err != redis.Nil{
return "", err
}
// If data is not found
if err == redis.Nil {
data,err = GetFromDB(ctx,id)
if err != nil{
return "", err
}
AsyncAdd(func() error{
return UpdateCache(ctx,id,data)
})
}
return data,nil
}
func GetFromRedis(ctx context.Context,id uint64) (string,error) {
// TODO: Get information from redis
return "",nil
}
func GetFromDB(ctx context.Context,id uint64) (string,error) {
// TODO: Get information from DB
return "",nil
}
func UpdateCache(ctx context.Context,id interface{},data string) error {
// TODO: Update cache information
return nil
}
func main() {
ctx,cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
_,err := GetInstance(ctx,2021)
if err != nil{
return
}
}
Reason: Asynchronous update failed due to misuse.
Analysis#
Let's analyze what this code does. It's actually quite simple. We want to retrieve some information. First, we try to get it from the cache. If it's not found in the cache, we retrieve it from the DB. After retrieving the information from the DB, we put the update cache method into a coroutine pool and asynchronously update the cache. The design seems perfect, but in actual work, asynchronous cache updates have never been successful...
The reason for the failure lies in this code snippet:
AsyncAdd(func() error{
return UpdateCache(ctx,id,data)
})
There is only one reason for the error, and that is the ctx. If we change it like this, everything will be fine.
AsyncAdd(func() error{
ctxAsync,cancel := context.WithTimeout(context.Background(),3 * time.Second)
defer cancel()
return UpdateCache(ctxAsync,id,data)
})
Reason#
In the original code, when the UpdateCache function is called, it uses the external context object ctx, which causes the timeout of the UpdateCache function to be affected by the context object passed in by the GetInstance function.
By creating a new context object ctxAsync in the anonymous function and passing it to the UpdateCache function, we can ensure that the timeout of the UpdateCache function is independent of the external context object.
At the same time, since ctxAsync is created inside the anonymous function, we need to call the cancel() function at the end of the function execution to release the related resources.