fix: close rows after query timeout to prevent hang by dlevy-msft-sql · Pull Request #702 · microsoft/go-sqlcmd
Fixes sqlcmd hanging for ~10 minutes after query timeout on Linux.
Problem
When using the -t (query timeout) flag, sqlcmd correctly prints "Timeout expired" but then hangs for approximately 10 minutes before exiting. This occurs because the rows result set from QueryContext is never closed when a timeout occurs.
Reproduction
# This should timeout after 1 second, but hangs for ~10 minutes sqlcmd -S server -t 1 -Q "WAITFOR DELAY '00:00:10'"
Root Cause
In pkg/sqlcmd/sqlcmd.go, the runQuery function calls db.QueryContext() but doesn't close the returned rows object. When the context times out, the query is cancelled but the rows handle remains open, causing the underlying connection to wait for the driver's internal cleanup timeout.
Fix
Add defer rows.Close() immediately after QueryContext returns:
rows, qe := s.db.QueryContext(ctx, query, retmsg) if rows != nil { defer func() { _ = rows.Close() }() }
This ensures the result set is always closed, even when the context is cancelled due to timeout.
Changes
| File | Change |
|---|---|
pkg/sqlcmd/sqlcmd.go |
Add defer rows.Close() after QueryContext |
cmd/modern/e2e_test.go |
Add TestE2E_QueryTimeout_NoHang regression test |
Testing
The new test verifies that a 1-second timeout query completes within 30 seconds (not 10 minutes):
func TestE2E_QueryTimeout_NoHang(t *testing.T) { // Uses -t 1 with WAITFOR DELAY '00:00:10' // Asserts command completes in < 30 seconds }
Related
Replaces #682 (closed due to lint error and code quality issues).