diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..e3e15746 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +If possible, please provide runnable example such as: + +```go +package main + +import ( + "context" + "log" + "os" + + "github.com/jackc/pgx/v4" +) + +func main() { + conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) + if err != nil { + log.Fatal(err) + } + defer conn.Close(context.Background()) + + // Your code here... +} +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Actual behavior** +A clear and concise description of what actually happened. + +**Version** + - Go: `$ go version` -> [e.g. go version go1.18.3 darwin/amd64] + - PostgreSQL: `$ psql --no-psqlrc --tuples-only -c 'select version()'` -> [e.g. PostgreSQL 14.4 on x86_64-apple-darwin21.5.0, compiled by Apple clang version 13.1.6 (clang-1316.0.21.2.5), 64-bit] + - pgx: `$ grep 'github.com/jackc/pgx/v[0-9]' go.mod` -> [e.g. v4.16.1] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/other-issues.md b/.github/ISSUE_TEMPLATE/other-issues.md new file mode 100644 index 00000000..27862a31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other-issues.md @@ -0,0 +1,10 @@ +--- +name: Other issues +about: Any issue that is not a bug or a feature request +title: '' +labels: '' +assignees: '' + +--- + +Please describe the issue in detail. If this is a question about how to use pgx please use discussions instead. diff --git a/conn.go b/conn.go index 6b84e13d..a95cff21 100644 --- a/conn.go +++ b/conn.go @@ -127,13 +127,13 @@ func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) { // Possible values: "cache_statement", "cache_describe", "describe_exec", "exec", and "simple_protocol". See // QueryExecMode constant documentation for the meaning of these values. Default: "cache_statement". // -// statement_cache_capacity -// The maximum size of the statement cache used when executing a query with "cache_statement" query exec mode. -// Default: 512. +// statement_cache_capacity +// The maximum size of the statement cache used when executing a query with "cache_statement" query exec mode. +// Default: 512. // -// description_cache_capacity -// The maximum size of the description cache used when executing a query with "cache_describe" query exec mode. -// Default: 512. +// description_cache_capacity +// The maximum size of the description cache used when executing a query with "cache_describe" query exec mode. +// Default: 512. func ParseConfig(connString string) (*ConnConfig, error) { config, err := pgconn.ParseConfig(connString) if err != nil { diff --git a/large_objects.go b/large_objects.go index baa92b38..c238ab9c 100644 --- a/large_objects.go +++ b/large_objects.go @@ -56,10 +56,10 @@ func (o *LargeObjects) Unlink(ctx context.Context, oid uint32) error { // A LargeObject is a large object stored on the server. It is only valid within the transaction that it was initialized // in. It uses the context it was initialized with for all operations. It implements these interfaces: // -// io.Writer -// io.Reader -// io.Seeker -// io.Closer +// io.Writer +// io.Reader +// io.Seeker +// io.Closer type LargeObject struct { ctx context.Context tx Tx diff --git a/pgxpool/pool.go b/pgxpool/pool.go index c7601a38..df017892 100644 --- a/pgxpool/pool.go +++ b/pgxpool/pool.go @@ -70,6 +70,16 @@ func (cr *connResource) getPoolRows(c *Conn, r pgx.Rows) *poolRows { return pr } +// detachedCtx wraps a context and will never be canceled, regardless of if +// the wrapped one is cancelled. The Err() method will never return any errors. +type detachedCtx struct { + context.Context +} + +func (detachedCtx) Done() <-chan struct{} { return nil } +func (detachedCtx) Deadline() (time.Time, bool) { return time.Time{}, false } +func (detachedCtx) Err() error { return nil } + // Pool allows for connection reuse. type Pool struct { p *puddle.Pool[*connResource] @@ -189,6 +199,19 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { p.p = puddle.NewPool( func(ctx context.Context) (*connResource, error) { + // we ignore cancellation on the original context because its either from + // the health check or its from a query and we don't want to cancel creating + // a connection just because the original query was cancelled since that + // could end up stampeding the server + // this will keep any Values in the original context and will just ignore + // cancellation + // see https://github.com/jackc/pgx/issues/1259 + ctx = detachedCtx{ctx} + + // But we do want to ensure that a connect won't hang forever. + ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + connConfig := p.config.ConnConfig if p.beforeConnect != nil { @@ -253,11 +276,11 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { // // See Config for definitions of these arguments. // -// # Example DSN -// user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca pool_max_conns=10 +// # Example DSN +// user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca pool_max_conns=10 // -// # Example URL -// postgres://jack:secret@pg.example.com:5432/mydb?sslmode=verify-ca&pool_max_conns=10 +// # Example URL +// postgres://jack:secret@pg.example.com:5432/mydb?sslmode=verify-ca&pool_max_conns=10 func ParseConfig(connString string) (*Config, error) { connConfig, err := pgx.ParseConfig(connString) if err != nil { @@ -651,7 +674,7 @@ func (p *Pool) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, er return nil, err } - return &Tx{t: t, c: c}, err + return &Tx{t: t, c: c}, nil } func (p *Pool) CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) { diff --git a/pgxpool/pool_test.go b/pgxpool/pool_test.go index cfebca7c..2ceb33cf 100644 --- a/pgxpool/pool_test.go +++ b/pgxpool/pool_test.go @@ -47,6 +47,32 @@ func TestParseConfigExtractsPoolArguments(t *testing.T) { assert.NotContains(t, config.ConnConfig.Config.RuntimeParams, "pool_min_conns") } +func TestConstructorIgnoresContext(t *testing.T) { + t.Parallel() + + config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) + assert.NoError(t, err) + var cancel func() + config.BeforeConnect = func(context.Context, *pgx.ConnConfig) error { + // cancel the query's context before we actually Dial to ensure the Dial's + // context isn't cancelled + cancel() + return nil + } + + pool, err := pgxpool.NewWithConfig(context.Background(), config) + require.NoError(t, err) + + assert.EqualValues(t, 0, pool.Stat().TotalConns()) + + var ctx context.Context + ctx, cancel = context.WithCancel(context.Background()) + defer cancel() + _, err = pool.Exec(ctx, "SELECT 1") + assert.ErrorIs(t, err, context.Canceled) + assert.EqualValues(t, 1, pool.Stat().TotalConns()) +} + func TestConnectConfigRequiresConnConfigFromParseConfig(t *testing.T) { t.Parallel()