Skip to content

Instantly share code, notes, and snippets.

@twpayne
Last active March 29, 2019 16:26
Show Gist options
  • Select an option

  • Save twpayne/f8e448f69944dbec1f05e2d3d7b3cd39 to your computer and use it in GitHub Desktop.

Select an option

Save twpayne/f8e448f69944dbec1f05e2d3d7b3cd39 to your computer and use it in GitHub Desktop.
github.com/lib/pq race condition
package main
// Run this with:
// $ go test -count=1 -race -timeout=2s -dsn=postgresql://... .
import (
"context"
"database/sql"
"flag"
"strconv"
"testing"
"time"
"github.com/lib/pq"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
dsn = flag.String("dsn", "postgresql://testuser:testpassword@localhost/testdb?sslmode=disable", "data source name")
)
func init() {
flag.Parse()
}
func TestRaceCondition(t *testing.T) {
// Connect to the database.
db, err := sql.Open("postgres", *dsn)
require.NoError(t, err)
defer db.Close()
// Create a table for this test and clean it up after.
tableName := "table_" + strconv.Itoa(int(time.Now().UnixNano()))
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS ` + tableName + ` (
id INT PRIMARY KEY
);
`)
require.NoError(t, err)
defer db.Exec(`
DROP TABLE ` + tableName + `;
`)
// Create a context that can be canceled.
ctx, cancel := context.WithCancel(context.Background())
// Start a context-aware transaction.
tx, err := db.BeginTx(ctx, nil)
// Using a non-context aware transaction will work around the race
// condition.
// tx, err := db.Begin()
require.NoError(t, err)
defer tx.Rollback()
// Prepare a COPY IN statement.
stmt, err := tx.Prepare(pq.CopyIn(tableName, "id"))
require.NoError(t, err)
defer stmt.Close()
// Create a channel to communicate generated ids.
ch := make(chan int)
// Spawn a goroutine to generate ids and write them to the channel. After
// sending a few, cancel the context.
go func(ch chan<- int) {
defer close(ch)
for id := 0; id < 4; id++ {
ch <- id
if id == 2 { // Replace 2 with -1 to avoid calling cancel.
cancel()
}
}
}(ch)
// Read ids from the channel and insert them until the channel is closed or
// the context is canceled.
FOR:
for {
select {
case <-ctx.Done():
require.Equal(t, context.Canceled, ctx.Err())
return
case id, ok := <-ch:
if !ok {
break FOR
}
_, err = stmt.Exec(id)
assert.NoError(t, err)
}
}
// Complete the COPY IN statment.
_, err = stmt.Exec()
require.NoError(t, err)
// Commit the transaction.
require.NoError(t, tx.Commit())
}
==================
WARNING: DATA RACE
Write at 0x00c000156004 by goroutine 13:
syscall.Read()
/usr/lib/go-1.12/src/internal/race/race.go:49 +0x9b
internal/poll.(*FD).Read()
/usr/lib/go-1.12/src/internal/poll/fd_unix.go:165 +0x1a5
net.(*netFD).Read()
/usr/lib/go-1.12/src/net/fd_unix.go:202 +0x65
net.(*conn).Read()
/usr/lib/go-1.12/src/net/net.go:177 +0xa1
net.(*TCPConn).Read()
<autogenerated>:1 +0x69
bufio.(*Reader).Read()
/usr/lib/go-1.12/src/bufio/bufio.go:223 +0x7bb
io.ReadAtLeast()
/usr/lib/go-1.12/src/io/io.go:310 +0x95
github.com/lib/pq.(*conn).recvMessage()
/usr/lib/go-1.12/src/io/io.go:329 +0x235
github.com/lib/pq.(*conn).recv1Buf()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:1011 +0x46
github.com/lib/pq.(*conn).simpleExec()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:1032 +0x28c
github.com/lib/pq.(*conn).Rollback()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:608 +0x119
database/sql.(*Tx).rollback.func1()
/usr/lib/go-1.12/src/database/sql/sql.go:2025 +0x70
database/sql.withLock()
/usr/lib/go-1.12/src/database/sql/sql.go:3097 +0x74
database/sql.(*Tx).rollback()
/usr/lib/go-1.12/src/database/sql/sql.go:2024 +0xc7
database/sql.(*Tx).awaitDone()
/usr/lib/go-1.12/src/database/sql/sql.go:1921 +0x83
Previous read at 0x00c000156004 by goroutine 14:
runtime.slicecopy()
/usr/lib/go-1.12/src/runtime/slice.go:197 +0x0
bufio.(*Reader).Read()
/usr/lib/go-1.12/src/bufio/bufio.go:234 +0x24c
io.ReadAtLeast()
/usr/lib/go-1.12/src/io/io.go:310 +0x95
github.com/lib/pq.(*conn).recvMessage()
/usr/lib/go-1.12/src/io/io.go:329 +0x235
github.com/lib/pq.(*copyin).resploop()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/copy.go:144 +0x8a
Goroutine 13 (running) created at:
database/sql.(*DB).beginDC()
/usr/lib/go-1.12/src/database/sql/sql.go:1671 +0x2e9
database/sql.(*DB).begin()
/usr/lib/go-1.12/src/database/sql/sql.go:1646 +0x129
database/sql.(*DB).BeginTx()
/usr/lib/go-1.12/src/database/sql/sql.go:1624 +0xa2
github.com/twpayne/go-playground/lib-pq-race-condition.TestRaceCondition()
/home/twp/src/github.com/twpayne/go-playground/lib-pq-race-condition/main_test.go:48 +0x420
testing.tRunner()
/usr/lib/go-1.12/src/testing/testing.go:865 +0x163
Goroutine 14 (running) created at:
github.com/lib/pq.(*conn).prepareCopyIn()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/copy.go:91 +0xbba
github.com/lib/pq.(*conn).Prepare()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:811 +0x1f6
database/sql.ctxDriverPrepare()
/usr/lib/go-1.12/src/database/sql/ctxutil.go:17 +0xb2
database/sql.(*driverConn).prepareLocked()
/usr/lib/go-1.12/src/database/sql/sql.go:440 +0xa8
database/sql.(*DB).prepareDC.func2()
/usr/lib/go-1.12/src/database/sql/sql.go:1396 +0x93
database/sql.withLock()
/usr/lib/go-1.12/src/database/sql/sql.go:3097 +0x74
database/sql.(*DB).prepareDC()
/usr/lib/go-1.12/src/database/sql/sql.go:1395 +0x16f
database/sql.(*Tx).PrepareContext()
/usr/lib/go-1.12/src/database/sql/sql.go:2058 +0x11d
github.com/twpayne/go-playground/lib-pq-race-condition.TestRaceCondition()
/usr/lib/go-1.12/src/database/sql/sql.go:2075 +0x55d
testing.tRunner()
/usr/lib/go-1.12/src/testing/testing.go:865 +0x163
==================
==================
WARNING: DATA RACE
Write at 0x00c000156005 by goroutine 13:
syscall.Read()
/usr/lib/go-1.12/src/internal/race/race.go:49 +0x9b
internal/poll.(*FD).Read()
/usr/lib/go-1.12/src/internal/poll/fd_unix.go:165 +0x1a5
net.(*netFD).Read()
/usr/lib/go-1.12/src/net/fd_unix.go:202 +0x65
net.(*conn).Read()
/usr/lib/go-1.12/src/net/net.go:177 +0xa1
net.(*TCPConn).Read()
<autogenerated>:1 +0x69
bufio.(*Reader).Read()
/usr/lib/go-1.12/src/bufio/bufio.go:223 +0x7bb
io.ReadAtLeast()
/usr/lib/go-1.12/src/io/io.go:310 +0x95
github.com/lib/pq.(*conn).recvMessage()
/usr/lib/go-1.12/src/io/io.go:329 +0x235
github.com/lib/pq.(*conn).recv1Buf()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:1011 +0x46
github.com/lib/pq.(*conn).simpleExec()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:1032 +0x28c
github.com/lib/pq.(*conn).Rollback()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:608 +0x119
database/sql.(*Tx).rollback.func1()
/usr/lib/go-1.12/src/database/sql/sql.go:2025 +0x70
database/sql.withLock()
/usr/lib/go-1.12/src/database/sql/sql.go:3097 +0x74
database/sql.(*Tx).rollback()
/usr/lib/go-1.12/src/database/sql/sql.go:2024 +0xc7
database/sql.(*Tx).awaitDone()
/usr/lib/go-1.12/src/database/sql/sql.go:1921 +0x83
Previous read at 0x00c000156005 by goroutine 14:
runtime.slicecopy()
/usr/lib/go-1.12/src/runtime/slice.go:197 +0x0
bufio.(*Reader).Read()
/usr/lib/go-1.12/src/bufio/bufio.go:234 +0x24c
io.ReadAtLeast()
/usr/lib/go-1.12/src/io/io.go:310 +0x95
github.com/lib/pq.(*conn).recvMessage()
/usr/lib/go-1.12/src/io/io.go:329 +0x3a0
github.com/lib/pq.(*copyin).resploop()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/copy.go:144 +0x8a
Goroutine 13 (running) created at:
database/sql.(*DB).beginDC()
/usr/lib/go-1.12/src/database/sql/sql.go:1671 +0x2e9
database/sql.(*DB).begin()
/usr/lib/go-1.12/src/database/sql/sql.go:1646 +0x129
database/sql.(*DB).BeginTx()
/usr/lib/go-1.12/src/database/sql/sql.go:1624 +0xa2
github.com/twpayne/go-playground/lib-pq-race-condition.TestRaceCondition()
/home/twp/src/github.com/twpayne/go-playground/lib-pq-race-condition/main_test.go:48 +0x420
testing.tRunner()
/usr/lib/go-1.12/src/testing/testing.go:865 +0x163
Goroutine 14 (running) created at:
github.com/lib/pq.(*conn).prepareCopyIn()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/copy.go:91 +0xbba
github.com/lib/pq.(*conn).Prepare()
/home/twp/pkg/mod/github.com/lib/pq@v1.0.0/conn.go:811 +0x1f6
database/sql.ctxDriverPrepare()
/usr/lib/go-1.12/src/database/sql/ctxutil.go:17 +0xb2
database/sql.(*driverConn).prepareLocked()
/usr/lib/go-1.12/src/database/sql/sql.go:440 +0xa8
database/sql.(*DB).prepareDC.func2()
/usr/lib/go-1.12/src/database/sql/sql.go:1396 +0x93
database/sql.withLock()
/usr/lib/go-1.12/src/database/sql/sql.go:3097 +0x74
database/sql.(*DB).prepareDC()
/usr/lib/go-1.12/src/database/sql/sql.go:1395 +0x16f
database/sql.(*Tx).PrepareContext()
/usr/lib/go-1.12/src/database/sql/sql.go:2058 +0x11d
github.com/twpayne/go-playground/lib-pq-race-condition.TestRaceCondition()
/usr/lib/go-1.12/src/database/sql/sql.go:2075 +0x55d
testing.tRunner()
/usr/lib/go-1.12/src/testing/testing.go:865 +0x163
==================
--- FAIL: TestRaceCondition (0.04s)
require.go:794:
Error Trace: main_test.go:88
Error: Received unexpected error:
sql: transaction has already been committed or rolled back
Test: TestRaceCondition
testing.go:809: race detected during execution of test
FAIL
FAIL github.com/twpayne/go-playground/lib-pq-race-condition 0.051s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment