Last active
March 29, 2019 16:26
-
-
Save twpayne/f8e448f69944dbec1f05e2d3d7b3cd39 to your computer and use it in GitHub Desktop.
github.com/lib/pq race condition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ================== | |
| 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