-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor Connector to expose Close method #151
Conversation
f122ad8
to
782e28c
Compare
@levakin Thanks for the PR. I've dug a little deeper under the hood and am quite sure, that we do not need to return When debugging the And directly after the breakpoint in the So to my understanding, the connector is closed correctly. What is missing, though, is the call to What do you think? |
I've added the |
@marcboeker I agree that there is an option for the developer to guess about type casting. But I'm still convinced that it shouldn't be hidden. It should be easy to do the right thing and then package would have good interface to work with. |
782e28c
to
024a64e
Compare
DuckDB destroys config when closing database. e.g. here https://github.com/duckdb/duckdb/blob/b58f184e1e87df5fc2d574996a6b63409df96f47/src/common/adbc/adbc.cpp#L218-L228
024a64e
to
9dfac4e
Compare
I favor the changes proposed in this PR - DuckDB's C API does not mind if a On another note, you (cc @marcboeker) added some of the changes in this PR to #149, which are now on Do you want me to open a separate PR to fix these leaks, or should we merge them as part of this PR? On another note, |
TLDR: I looked more into this, also w.r.t. #153. We do not need to keep the configuration alive after opening a DuckDB database. However, it is crucial to explicitly close the connector, as we leak memory otherwise. This is possible with I am currently finishing up a PR that (1) uses the changes of this PR to Now, my more in-depth analysis of this. @marcboeker, here, you say that the breakpoint in func (c *connector) Close() error {
C.duckdb_close(c.db)
c.db = nil
return nil
} However, we are only closing the in-memory database that we opened with Given the following extracted test, we never hit func TestConnectorLeaks(t *testing.T) {
t.Parallel()
t.Run("select series", func(t *testing.T) {
c, err := NewConnector("", nil)
require.NoError(t, err)
// defer c.(io.Closer).Close()
conn, err := c.Connect(context.Background())
require.NoError(t, err)
defer conn.Close()
ar, err := NewArrowFromConn(conn)
require.NoError(t, err)
rdr, err := ar.QueryContext(context.Background(), "SELECT * FROM generate_series(1, 10)")
require.NoError(t, err)
defer rdr.Release()
for rdr.Next() {
rec := rdr.Record()
require.Equal(t, int64(10), rec.NumRows())
require.NoError(t, err)
}
require.NoError(t, rdr.Err())
})
t.Run("select long series", func(t *testing.T) {
c, err := NewConnector("", nil)
require.NoError(t, err)
// defer c.(io.Closer).Close()
conn, err := c.Connect(context.Background())
require.NoError(t, err)
defer conn.Close()
ar, err := NewArrowFromConn(conn)
require.NoError(t, err)
rdr, err := ar.QueryContext(context.Background(), "SELECT * FROM generate_series(1, 10000)")
require.NoError(t, err)
defer rdr.Release()
var totalRows int64
for rdr.Next() {
rec := rdr.Record()
totalRows += rec.NumRows()
}
require.Equal(t, int64(10000), totalRows)
require.NoError(t, rdr.Err())
})
} We can even see this in the leak profiler, which runs the test multiple times - we leak in |
I've used the "select series" test just to check if the Close method of the connector is called, which is true. I have not verified that all opened connections in the test were closed properly. However, since the Close method is now exposed, we can control the closing manually. |
At the moment Connector is stateful but cannot be closed explicitly.
connector
type is hidden behinddriver.Connector
.And to get access to Close method it's needed to make type assertion.
Let me give more concrete example regarding connector.Close.
Let's take README example.
Here connector is opened and Database is opened internally. The bug here is that connector is never closed. Unless it's passed to
db := sql.OpenDB()
and then closed withdb.Close()
.Here's part of sql package code:
We end up with 2 options, leave it as is and work with database only through standard sql package or expose connector Close method to the outer world. It can be done by returning
*Connector
. Also in this case we should ensure Connector is fine with multiple close calls.golang/go#41790
Here's this check https://go-review.googlesource.com/c/go/+/258360/6/src/database/sql/sql.go
from https://go-review.googlesource.com/c/go/+/258360
Returning concrete type
*Connector
is suggested by CodeReviewComments https://go.dev/wiki/CodeReviewComments#interfaces