You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

取消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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 09:44:56