Fix "cannot start a transaction within a transaction" issue (#764) (#765)

* Fix "cannot start a transaction within a transaction" issue

[why]
If db.BeginTx(ctx, nil) context is cancelled too fast, "BEGIN" statement can be
completed inside DB, but we still try to cancel it with sqlite3_interrupt.
In such case we get context.Cancelled or context.DeadlineExceeded from exec(),
but operation really completed. Connection returned into pool, and returns "cannot
start a transaction within a transaction" error for next db.BeginTx() call.

[how]
Handle status code returned from cancelled operation.

[testing]
Added unit-test which reproduces issue.

* Reduce TestQueryRowContextCancelParallel concurrency

[why]
Tests times out in travis-ci when run with -race option.
This commit is contained in:
Andrii Zavorotnii
2020-08-28 08:43:21 -07:00
committed by GitHub
parent 6a8b30186d
commit 862b95943f
3 changed files with 129 additions and 6 deletions

View File

@@ -136,6 +136,44 @@ func TestShortTimeout(t *testing.T) {
}
}
func TestExecContextCancel(t *testing.T) {
srcTempFilename := TempFilename(t)
defer os.Remove(srcTempFilename)
db, err := sql.Open("sqlite3", srcTempFilename)
if err != nil {
t.Fatal(err)
}
defer db.Close()
ts := time.Now()
initDatabase(t, db, 1000)
spent := time.Since(ts)
if spent < 100*time.Millisecond {
t.Skip("test will be too racy, as ExecContext below will be too fast.")
}
// expected to be extremely slow query
q := `
INSERT INTO test_table (key1, key_id, key2, key3, key4, key5, key6, data)
SELECT t1.key1 || t2.key1, t1.key_id || t2.key_id, t1.key2 || t2.key2, t1.key3 || t2.key3, t1.key4 || t2.key4, t1.key5 || t2.key5, t1.key6 || t2.key6, t1.data || t2.data
FROM test_table t1 LEFT OUTER JOIN test_table t2`
// expect query above take ~ same time as setup above
ctx, cancel := context.WithTimeout(context.Background(), spent/2)
defer cancel()
ts = time.Now()
r, err := db.ExecContext(ctx, q)
// racy check
if r != nil {
n, err := r.RowsAffected()
t.Log(n, err, time.Since(ts))
}
if err != context.DeadlineExceeded {
t.Fatal(err, ctx.Err())
}
}
func TestQueryRowContextCancel(t *testing.T) {
srcTempFilename := TempFilename(t)
defer os.Remove(srcTempFilename)
@@ -191,7 +229,7 @@ func TestQueryRowContextCancelParallel(t *testing.T) {
testCtx, cancel := context.WithCancel(context.Background())
defer cancel()
for i := 0; i < 50; i++ {
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()