diff --git a/conn.go b/conn.go index fd560985..60bdca25 100644 --- a/conn.go +++ b/conn.go @@ -82,7 +82,12 @@ func newConn(ctx context.Context, filename string, flags OpenFlag) (ret *Conn, _ } defer func() { if ret == nil { - c.Close() + if c.handle == 0 { + // Close would be a no-op; release the wrapper directly. + c.wrp.Close() + } else { + c.Close() + } } else { c.interrupt = context.Background() } diff --git a/tests/conn_test.go b/tests/conn_test.go index 7a915801..ffa830b6 100644 --- a/tests/conn_test.go +++ b/tests/conn_test.go @@ -30,6 +30,37 @@ func TestConn_Open_dir(t *testing.T) { } } +func TestConn_Open_notfound_leak(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("skipping on non-Linux") + } + // Not parallel: counts the process' memory mappings. + + mappings := func() int { + data, err := os.ReadFile("/proc/self/maps") + if err != nil { + t.Fatal(err) + } + return strings.Count(string(data), "\n") + } + + const n = 20 + before := mappings() + for range n { + _, err := sqlite3.OpenFlags("test.db", sqlite3.OPEN_READONLY) + if err == nil { + t.Fatal("want error") + } + } + after := mappings() + + // Each leaked connection adds at least one mapping; + // allow some slack for unrelated runtime activity. + if after-before >= n { + t.Errorf("mappings grew from %d to %d after %d failed opens", before, after, n) + } +} + func TestConn_Open_notfound(t *testing.T) { t.Parallel()