diff options
Diffstat (limited to 'libgo/go/database/sql/sql_test.go')
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 125 |
1 files changed, 102 insertions, 23 deletions
diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index 898df3b455b2..450e5f1f8c96 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -153,8 +153,13 @@ func closeDB(t testing.TB, db *DB) { if err != nil { t.Fatalf("error closing DB: %v", err) } - if count := db.numOpenConns(); count != 0 { - t.Fatalf("%d connections still open after closing DB", count) + + var numOpen int + if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool { + numOpen = db.numOpenConns() + return numOpen == 0 + }) { + t.Fatalf("%d connections still open after closing DB", numOpen) } } @@ -276,6 +281,7 @@ func TestQuery(t *testing.T) { } } +// TestQueryContext tests canceling the context while scanning the rows. func TestQueryContext(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -297,7 +303,7 @@ func TestQueryContext(t *testing.T) { for rows.Next() { if index == 2 { cancel() - time.Sleep(10 * time.Millisecond) + waitForRowsClose(t, rows, 5*time.Second) } var r row err = rows.Scan(&r.age, &r.name) @@ -313,9 +319,13 @@ func TestQueryContext(t *testing.T) { got = append(got, r) index++ } - err = rows.Err() - if err != nil { - t.Fatalf("Err: %v", err) + select { + case <-ctx.Done(): + if err := ctx.Err(); err != context.Canceled { + t.Fatalf("context err = %v; want context.Canceled") + } + default: + t.Fatalf("context err = nil; want context.Canceled") } want := []row{ {age: 1, name: "Alice"}, @@ -327,6 +337,7 @@ func TestQueryContext(t *testing.T) { // And verify that the final rows.Next() call, which hit EOF, // also closed the rows connection. + waitForRowsClose(t, rows, 5*time.Second) waitForFree(t, db, 5*time.Second, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { t.Errorf("executed %d Prepare statements; want 1", prepares) @@ -356,12 +367,27 @@ func waitForFree(t *testing.T, db *DB, maxWait time.Duration, want int) { } } +func waitForRowsClose(t *testing.T, rows *Rows, maxWait time.Duration) { + if !waitCondition(maxWait, 5*time.Millisecond, func() bool { + rows.closemu.RLock() + defer rows.closemu.RUnlock() + return rows.closed + }) { + t.Fatal("failed to close rows") + } +} + +// TestQueryContextWait ensures that rows and all internal statements are closed when +// a query context is closed during execution. func TestQueryContextWait(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) prepares0 := numPrepares(t, db) - ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*15) + // TODO(kardianos): convert this from using a timeout to using an explicit + // cancel when the query signals that is is "executing" the query. + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) + defer cancel() // This will trigger the *fakeConn.Prepare method which will take time // performing the query. The ctxDriverPrepare func will check the context @@ -374,10 +400,15 @@ func TestQueryContextWait(t *testing.T) { // Verify closed rows connection after error condition. waitForFree(t, db, 5*time.Second, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { - t.Errorf("executed %d Prepare statements; want 1", prepares) + // TODO(kardianos): if the context timeouts before the db.QueryContext + // executes this check may fail. After adjusting how the context + // is canceled above revert this back to a Fatal error. + t.Logf("executed %d Prepare statements; want 1", prepares) } } +// TestTxContextWait tests the transaction behavior when the tx context is canceled +// during execution of the query. func TestTxContextWait(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -386,6 +417,10 @@ func TestTxContextWait(t *testing.T) { tx, err := db.BeginTx(ctx, nil) if err != nil { + // Guard against the context being canceled before BeginTx completes. + if err == context.DeadlineExceeded { + t.Skip("tx context canceled prior to first use") + } t.Fatal(err) } @@ -398,12 +433,6 @@ func TestTxContextWait(t *testing.T) { } waitForFree(t, db, 5*time.Second, 0) - - // Ensure the dropped connection allows more connections to be made. - // Checked on DB Close. - waitCondition(5*time.Second, 5*time.Millisecond, func() bool { - return db.numOpenConns() == 0 - }) } func TestMultiResultSetQuery(t *testing.T) { @@ -527,6 +556,63 @@ func TestQueryNamedArg(t *testing.T) { } } +func TestPoolExhaustOnCancel(t *testing.T) { + if testing.Short() { + t.Skip("long test") + } + db := newTestDB(t, "people") + defer closeDB(t, db) + + max := 3 + + db.SetMaxOpenConns(max) + + // First saturate the connection pool. + // Then start new requests for a connection that is cancelled after it is requested. + + var saturate, saturateDone sync.WaitGroup + saturate.Add(max) + saturateDone.Add(max) + + for i := 0; i < max; i++ { + go func() { + saturate.Done() + rows, err := db.Query("WAIT|500ms|SELECT|people|name,photo|") + if err != nil { + t.Fatalf("Query: %v", err) + } + rows.Close() + saturateDone.Done() + }() + } + + saturate.Wait() + + // Now cancel the request while it is waiting. + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + + for i := 0; i < max; i++ { + ctxReq, cancelReq := context.WithCancel(ctx) + go func() { + time.Sleep(time.Millisecond * 100) + cancelReq() + }() + err := db.PingContext(ctxReq) + if err != context.Canceled { + t.Fatalf("PingContext (Exhaust): %v", err) + } + } + + saturateDone.Wait() + + // Now try to open a normal connection. + err := db.PingContext(ctx) + if err != nil { + t.Fatalf("PingContext (Normal): %v", err) + } +} + func TestByteOwnership(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -2677,7 +2763,6 @@ func TestIssue18429(t *testing.T) { }() } wg.Wait() - time.Sleep(milliWait * 3 * time.Millisecond) } // TestIssue18719 closes the context right before use. The sql.driverConn @@ -2720,14 +2805,8 @@ func TestIssue18719(t *testing.T) { // Do not explicitly rollback. The rollback will happen from the // canceled context. - // Wait for connections to return to pool. - var numOpen int - if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool { - numOpen = db.numOpenConns() - return numOpen == 0 - }) { - t.Fatalf("open conns after hitting EOF = %d; want 0", numOpen) - } + cancel() + waitForRowsClose(t, rows, 5*time.Second) } func TestConcurrency(t *testing.T) { |