How to set timeout for long running queries in gorm

Viewed 3928

Is there a way I can set gorm to time out after a configurable period of time when running a long query? I am using mssql. I have looked through the documentation and haven't discovered a way yet.

3 Answers

This code seems to work for me and is pretty clean. Just use transactions I guess.

    //Gorm query below
query = query.Where(whereClause)
//Set up Timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var x sql.TxOptions
db := query.BeginTx(ctx, &x)
defer db.Commit()
// Execute the query
if err := db.Find(*results).Error; err != nil {
    return err
}

By using WithContext from *gorm.DB you can pass a Timeout Context to Gorm:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var products []Product
db.WithContext(ctx).Find(&products)

gorm at the moment seems is not implementing any query that accepts as a parameter a context.Context, as e.g. QueryRowContext is doing.
You can create a workaround and use a Context to make "expirable" your query.

type QueryResponse struct {
    MyResult *MyResult
    Error    error
}

func queryHelper(ctx context.Context) <- chan *QueryResponse {
    chResult := make(chan *QueryResponse, 1)
    go func() {
        //your query here
        //...
        //blah blah check stuff do whatever you want
        //err is an error that comes from the query code
        if err != nil {
            chResult <- &QueryResponse{nil, err}
            return
        }
        chResult <- &QueryResponse{queryResponse, nil}
    } ()
    return chResult
}

func MyQueryFunction(ctx context.Context) (*MyResult, error) {
    select {
    case <-ctx.Done():
        return nil, fmt.Errorf("context timeout, query out of time")

    case res := <-queryHelper(ctx):
        return res.MyResult, res.Error
    }
}

And then in your upper function, whatever is it you can create a context and pass it to the MyQueryFunction. If the query exceeds the time you have set an error is raised and you should (must) check it.

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
res, err := MyQueryFunction(ctx)
if err != nil {
    fmt.Printf("err %v", err)
} else {
    fmt.Printf("res %v", res)
}

Of course, it is an example, is not considering a lot of use cases and a proper implementation inside the framework must be preferred.

Related