aboutsummaryrefslogtreecommitdiff
path: root/sqlite3.go
diff options
context:
space:
mode:
authorAndrii Zavorotnii <andrii.zavorotnii@gmail.com>2020-08-28 08:43:21 -0700
committerGitHub <noreply@github.com>2020-08-29 00:43:21 +0900
commit862b95943f99f3b40e317a79d41c27ac4b742011 (patch)
tree8ddf5deb99c4466eed725e130060083b29558e55 /sqlite3.go
parentUse go-pointer instead of uintptr hacks. (#814) (diff)
downloadgolite-862b95943f99f3b40e317a79d41c27ac4b742011.tar.gz
golite-862b95943f99f3b40e317a79d41c27ac4b742011.tar.xz
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.
Diffstat (limited to 'sqlite3.go')
-rw-r--r--sqlite3.go21
1 files changed, 16 insertions, 5 deletions
diff --git a/sqlite3.go b/sqlite3.go
index ababcfd..63e1c4f 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -1918,6 +1918,14 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
return s.exec(context.Background(), list)
}
+func isInterruptErr(err error) bool {
+ sqliteErr, ok := err.(Error)
+ if ok {
+ return sqliteErr.Code == ErrInterrupt
+ }
+ return false
+}
+
// exec executes a query that doesn't return rows. Attempts to honor context timeout.
func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) {
if ctx.Done() == nil {
@@ -1933,19 +1941,22 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
r, err := s.execSync(args)
resultCh <- result{r, err}
}()
+ var rv result
select {
- case rv := <-resultCh:
- return rv.r, rv.err
+ case rv = <-resultCh:
case <-ctx.Done():
select {
- case <-resultCh: // no need to interrupt
+ case rv = <-resultCh: // no need to interrupt, operation completed in db
default:
// this is still racy and can be no-op if executed between sqlite3_* calls in execSync.
C.sqlite3_interrupt(s.c.db)
- <-resultCh // ensure goroutine completed
+ rv = <-resultCh // wait for goroutine completed
+ if isInterruptErr(rv.err) {
+ return nil, ctx.Err()
+ }
}
- return nil, ctx.Err()
}
+ return rv.r, rv.err
}
func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) {