取消WithTimeout返回的Context为何影响父Context及部分子调用?
Go Context Cancel 行为异常问题
我遇到了一个Context使用的奇怪问题:当在DeleteAllSpecificUserSessionIds函数里直接调用cancel()而非用defer cancel()时,DeleteSessionId方法只能成功执行一次,后续调用都会返回“context cancelled”错误。比如数据库里有5个session ID,只能删掉1个;但换成defer cancel()后,所有session都能被正常删除。
错误实现(仅删除1个Session)
func DeleteSessionId(id string, ctx *context.Context) (err error) { sqlStatement := "DELETE FROM sessions WHERE id = $1" queryCtx, cancel := context.WithTimeout(*ctx, 60*time.Second) defer cancel() _, err = Constants.Db.ExecContext(queryCtx, sqlStatement, id) if err != nil { log.Println("An error occured while deleting session from sessions table: " + err.Error()) return err } return nil } func DeleteAllSpecificUserSessionIds(userId string, ctx context.Context) (err error) { sqlStatement := "SELECT id from sessions where userid = $1" queryCtx, cancel := context.WithTimeout(ctx, 60*time.Second) sqlRows, err := Constants.Db.QueryContext(queryCtx, sqlStatement, userId) cancel() if err != nil { log.Println("An error occurred when selecting sessions from DB: " + err.Error()) return err } var wg sync.WaitGroup for sqlRows.Next() { wg.Add(1) var sessionId string if err := sqlRows.Scan(&sessionId); err != nil { log.Println("An error occurred when looping through sessions from DB: " + err.Error()) return err } var key = CacheKeys.SessionIdKey{ SessionId: sessionId, } go func() { Constants.Cache.Remove(key) DeleteSessionId(sessionId, &ctx) wg.Done() }() } wg.Wait() if err := sqlRows.Err(); err != nil { log.Println("An error occurred when selecting sessions from DB: " + err.Error()) return err } sqlRows.Close() return nil }
正确实现(删除所有Session)
func DeleteSessionId(id string, ctx *context.Context) (err error) { sqlStatement := "DELETE FROM sessions WHERE id = $1" queryCtx, cancel := context.WithTimeout(*ctx, 60*time.Second) defer cancel() _, err = Constants.Db.ExecContext(queryCtx, sqlStatement, id) if err != nil { log.Println("An error occured while deleting session from sessions table: " + err.Error()) return err } return nil } func DeleteAllSpecificUserSessionIds(userId string, ctx context.Context) (err error) { sqlStatement := "SELECT id from sessions where userid = $1" queryCtx, cancel := context.WithTimeout(ctx, 60*time.Second) sqlRows, err := Constants.Db.QueryContext(queryCtx, sqlStatement, userId) defer cancel() if err != nil { log.Println("An error occurred when selecting sessions from DB: " + err.Error()) return err } var wg sync.WaitGroup for sqlRows.Next() { wg.Add(1) var sessionId string if err := sqlRows.Scan(&sessionId); err != nil { log.Println("An error occurred when looping through sessions from DB: " + err.Error()) return err } var key = CacheKeys.SessionIdKey{ SessionId: sessionId, } go func() { Constants.Cache.Remove(key) DeleteSessionId(sessionId, &ctx) wg.Done() }() } wg.Wait() if err := sqlRows.Err(); err != nil { log.Println("An error occurred when selecting sessions from DB: " + err.Error()) return err } sqlRows.Close() return nil }
我之前以为context.WithTimeout返回的是父Context的引用,要是这样的话,调用cancel()应该会让所有DeleteSessionId的调用都失败,但实际只有第一次成功,后续都失败。查了Context的官方文档,没找到关于创建父Context副本的说明,想搞清楚这个现象的原因。
内容的提问来源于stack exchange,提问作者TriedAtLeast
相关产品推荐
相关产品推荐

