From: Bryan C. Mills Date: Thu, 13 Jan 2022 18:57:47 +0000 (-0500) Subject: database/sql: consolidate test polling loops X-Git-Tag: go1.18beta2~101 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6891d07ee6a34f1c8d0326f3f7dd941bddf524f1;p=gostls13.git database/sql: consolidate test polling loops Also eliminate some arbitrary deadlines and sleeps. Fixes #49958 Change-Id: I999b39a896e430e3bb93aa8b8c9444f28bbaa9d0 Reviewed-on: https://go-review.googlesource.com/c/go/+/378395 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Daniel Theophanes --- diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go index 1bb9afc407..08ca1f5b9a 100644 --- a/src/database/sql/sql_test.go +++ b/src/database/sql/sql_test.go @@ -55,6 +55,10 @@ func init() { } } +// pollDuration is an arbitrary interval to wait between checks when polling for +// a condition to occur. +const pollDuration = 5 * time.Millisecond + const fakeDBName = "foo" var chrisBirthday = time.Unix(123456789, 0) @@ -173,7 +177,7 @@ func closeDB(t testing.TB, db *DB) { } var numOpen int - if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool { + if !waitCondition(t, func() bool { numOpen = db.numOpenConns() return numOpen == 0 }) { @@ -197,16 +201,14 @@ func (db *DB) numDeps() int { } // Dependencies are closed via a goroutine, so this polls waiting for -// numDeps to fall to want, waiting up to d. -func (db *DB) numDepsPollUntil(want int, d time.Duration) int { - deadline := time.Now().Add(d) - for { - n := db.numDeps() - if n <= want || time.Now().After(deadline) { - return n - } - time.Sleep(50 * time.Millisecond) - } +// numDeps to fall to want, waiting up to nearly the test's deadline. +func (db *DB) numDepsPoll(t *testing.T, want int) int { + var n int + waitCondition(t, func() bool { + n = db.numDeps() + return n <= want + }) + return n } func (db *DB) numFreeConns() int { @@ -229,7 +231,7 @@ func (db *DB) clearAllConns(t *testing.T) { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(0, time.Second); n > 0 { + if n := db.numDepsPoll(t, 0); n > 0 { t.Errorf("number of dependencies = %d; expected 0", n) db.dumpDeps(t) } @@ -321,7 +323,7 @@ func TestQueryContext(t *testing.T) { for rows.Next() { if index == 2 { cancel() - waitForRowsClose(t, rows, 5*time.Second) + waitForRowsClose(t, rows) } var r row err = rows.Scan(&r.age, &r.name) @@ -355,29 +357,43 @@ 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) + waitForRowsClose(t, rows) + waitForFree(t, db, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { t.Errorf("executed %d Prepare statements; want 1", prepares) } } -func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool { - deadline := time.Now().Add(waitFor) - for time.Now().Before(deadline) { +func waitCondition(t testing.TB, fn func() bool) bool { + timeout := 5 * time.Second + + type deadliner interface { + Deadline() (time.Time, bool) + } + if td, ok := t.(deadliner); ok { + if deadline, ok := td.Deadline(); ok { + timeout = time.Until(deadline) + timeout = timeout * 19 / 20 // Give 5% headroom for cleanup and error-reporting. + } + } + + deadline := time.Now().Add(timeout) + for { if fn() { return true } - time.Sleep(checkEvery) + if time.Until(deadline) < pollDuration { + return false + } + time.Sleep(pollDuration) } - return false } // waitForFree checks db.numFreeConns until either it equals want or // the maxWait time elapses. -func waitForFree(t *testing.T, db *DB, maxWait time.Duration, want int) { +func waitForFree(t *testing.T, db *DB, want int) { var numFree int - if !waitCondition(maxWait, 5*time.Millisecond, func() bool { + if !waitCondition(t, func() bool { numFree = db.numFreeConns() return numFree == want }) { @@ -385,8 +401,8 @@ 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 { +func waitForRowsClose(t *testing.T, rows *Rows) { + if !waitCondition(t, func() bool { rows.closemu.RLock() defer rows.closemu.RUnlock() return rows.closed @@ -416,7 +432,7 @@ func TestQueryContextWait(t *testing.T) { } // Verify closed rows connection after error condition. - waitForFree(t, db, 5*time.Second, 1) + waitForFree(t, db, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { // TODO(kardianos): if the context timeouts before the db.QueryContext // executes this check may fail. After adjusting how the context @@ -451,7 +467,7 @@ func TestTxContextWait(t *testing.T) { t.Fatalf("expected QueryContext to error with context canceled but returned %v", err) } - waitForFree(t, db, 5*time.Second, 0) + waitForFree(t, db, 0) } // TestTxContextWaitNoDiscard is the same as TestTxContextWait, but should not discard @@ -480,7 +496,7 @@ func TestTxContextWaitNoDiscard(t *testing.T) { t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err) } - waitForFree(t, db, 5*time.Second, 1) + waitForFree(t, db, 1) } // TestUnsupportedOptions checks that the database fails when a driver that @@ -565,7 +581,7 @@ func TestMultiResultSetQuery(t *testing.T) { // And verify that the final rows.Next() call, which hit EOF, // also closed the rows connection. - waitForFree(t, db, 5*time.Second, 1) + waitForFree(t, db, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { t.Errorf("executed %d Prepare statements; want 1", prepares) } @@ -2083,18 +2099,15 @@ func TestMaxOpenConns(t *testing.T) { } }() } - // Sleep for twice the expected length of time for the - // batch of 50 queries above to finish before starting - // the next round. - time.Sleep(2 * sleepMillis * time.Millisecond) + // Wait for the batch of queries above to finish before starting the next round. + wg.Wait() } - wg.Wait() if g, w := db.numFreeConns(), 10; g != w { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(20, time.Second); n > 20 { + if n := db.numDepsPoll(t, 20); n > 20 { t.Errorf("number of dependencies = %d; expected <= 20", n) db.dumpDeps(t) } @@ -2119,7 +2132,7 @@ func TestMaxOpenConns(t *testing.T) { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(10, time.Second); n > 10 { + if n := db.numDepsPoll(t, 10); n > 10 { t.Errorf("number of dependencies = %d; expected <= 10", n) db.dumpDeps(t) } @@ -2130,7 +2143,7 @@ func TestMaxOpenConns(t *testing.T) { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(5, time.Second); n > 5 { + if n := db.numDepsPoll(t, 5); n > 5 { t.Errorf("number of dependencies = %d; expected 0", n) db.dumpDeps(t) } @@ -2141,7 +2154,7 @@ func TestMaxOpenConns(t *testing.T) { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(5, time.Second); n > 5 { + if n := db.numDepsPoll(t, 5); n > 5 { t.Errorf("number of dependencies = %d; expected 0", n) db.dumpDeps(t) } @@ -2400,13 +2413,14 @@ func TestConnMaxLifetime(t *testing.T) { tx2.Commit() // Give connectionCleaner chance to run. - for i := 0; i < 100 && closes != 1; i++ { - time.Sleep(time.Millisecond) + waitCondition(t, func() bool { driver.mu.Lock() opens = driver.openCount - opens0 closes = driver.closeCount - closes0 driver.mu.Unlock() - } + + return closes == 1 + }) if opens != 3 { t.Errorf("opens = %d; want 3", opens) @@ -2466,18 +2480,15 @@ func TestStmtCloseDeps(t *testing.T) { } }() } - // Sleep for twice the expected length of time for the - // batch of 50 queries above to finish before starting - // the next round. - time.Sleep(2 * sleepMillis * time.Millisecond) + // Wait for the batch of queries above to finish before starting the next round. + wg.Wait() } - wg.Wait() if g, w := db.numFreeConns(), 2; g != w { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(4, time.Second); n > 4 { + if n := db.numDepsPoll(t, 4); n > 4 { t.Errorf("number of dependencies = %d; expected <= 4", n) db.dumpDeps(t) } @@ -2496,7 +2507,7 @@ func TestStmtCloseDeps(t *testing.T) { db.dumpDeps(t) } - if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool { + if !waitCondition(t, func() bool { return len(stmt.css) <= nquery }) { t.Errorf("len(stmt.css) = %d; want <= %d", len(stmt.css), nquery) @@ -2510,7 +2521,7 @@ func TestStmtCloseDeps(t *testing.T) { t.Errorf("free conns = %d; want %d", g, w) } - if n := db.numDepsPollUntil(2, time.Second); n > 2 { + if n := db.numDepsPoll(t, 2); n > 2 { t.Errorf("number of dependencies = %d; expected <= 2", n) db.dumpDeps(t) } @@ -2942,7 +2953,7 @@ func TestConnExpiresFreshOutOfPool(t *testing.T) { if ct > 0 { return } - time.Sleep(10 * time.Millisecond) + time.Sleep(pollDuration) } }() @@ -3721,7 +3732,7 @@ func TestIssue18719(t *testing.T) { // Wait for the context to cancel and tx to rollback. for tx.isDone() == false { - time.Sleep(3 * time.Millisecond) + time.Sleep(pollDuration) } } defer func() { hookTxGrabConn = nil }()