mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-10-31 22:40:01 +00:00
[chore]: Bump github.com/jackc/pgx/v5 from 5.4.3 to 5.5.0 (#2336)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
74b600655d
commit
9b76afc851
47 changed files with 2905 additions and 162 deletions
4
go.mod
4
go.mod
|
@ -34,7 +34,7 @@ require (
|
||||||
github.com/gorilla/feeds v1.1.1
|
github.com/gorilla/feeds v1.1.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/jackc/pgx/v5 v5.4.3
|
github.com/jackc/pgx/v5 v5.5.0
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/miekg/dns v1.1.56
|
github.com/miekg/dns v1.1.56
|
||||||
github.com/minio/minio-go/v7 v7.0.63
|
github.com/minio/minio-go/v7 v7.0.63
|
||||||
|
@ -122,6 +122,7 @@ require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
@ -160,6 +161,7 @@ require (
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
golang.org/x/tools v0.13.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -340,8 +340,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
|
||||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
|
14
vendor/github.com/jackc/pgx/v5/CHANGELOG.md
generated
vendored
14
vendor/github.com/jackc/pgx/v5/CHANGELOG.md
generated
vendored
|
@ -1,3 +1,17 @@
|
||||||
|
# 5.5.0 (November 4, 2023)
|
||||||
|
|
||||||
|
* Add CollectExactlyOneRow. (Julien GOTTELAND)
|
||||||
|
* Add OpenDBFromPool to create *database/sql.DB from *pgxpool.Pool. (Lev Zakharov)
|
||||||
|
* Prepare can automatically choose statement name based on sql. This makes it easier to explicitly manage prepared statements.
|
||||||
|
* Statement cache now uses deterministic, stable statement names.
|
||||||
|
* database/sql prepared statement names are deterministically generated.
|
||||||
|
* Fix: SendBatch wasn't respecting context cancellation.
|
||||||
|
* Fix: Timeout error from pipeline is now normalized.
|
||||||
|
* Fix: database/sql encoding json.RawMessage to []byte.
|
||||||
|
* CancelRequest: Wait for the cancel request to be acknowledged by the server. This should improve PgBouncer compatibility. (Anton Levakin)
|
||||||
|
* stdlib: Use Ping instead of CheckConn in ResetSession
|
||||||
|
* Add json.Marshaler and json.Unmarshaler for Float4, Float8 (Kirill Mironov)
|
||||||
|
|
||||||
# 5.4.3 (August 5, 2023)
|
# 5.4.3 (August 5, 2023)
|
||||||
|
|
||||||
* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
|
* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
|
||||||
|
|
6
vendor/github.com/jackc/pgx/v5/README.md
generated
vendored
6
vendor/github.com/jackc/pgx/v5/README.md
generated
vendored
|
@ -86,9 +86,13 @@ It is also possible to use the `database/sql` interface and convert a connection
|
||||||
|
|
||||||
See CONTRIBUTING.md for setup instructions.
|
See CONTRIBUTING.md for setup instructions.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube.com/watch?v=sXMSWhcHCf8) for a description of pgx architecture.
|
||||||
|
|
||||||
## Supported Go and PostgreSQL Versions
|
## Supported Go and PostgreSQL Versions
|
||||||
|
|
||||||
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.19 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.20 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
||||||
|
|
||||||
## Version Policy
|
## Version Policy
|
||||||
|
|
||||||
|
|
80
vendor/github.com/jackc/pgx/v5/conn.go
generated
vendored
80
vendor/github.com/jackc/pgx/v5/conn.go
generated
vendored
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -35,7 +37,7 @@ type ConnConfig struct {
|
||||||
|
|
||||||
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
||||||
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
||||||
// PGBouncer. In this case it may be preferrable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
// PGBouncer. In this case it may be preferable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
||||||
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
||||||
DefaultQueryExecMode QueryExecMode
|
DefaultQueryExecMode QueryExecMode
|
||||||
|
|
||||||
|
@ -99,8 +101,12 @@ func (ident Identifier) Sanitize() string {
|
||||||
return strings.Join(parts, ".")
|
return strings.Join(parts, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNoRows occurs when rows are expected but none are returned.
|
var (
|
||||||
var ErrNoRows = errors.New("no rows in result set")
|
// ErrNoRows occurs when rows are expected but none are returned.
|
||||||
|
ErrNoRows = errors.New("no rows in result set")
|
||||||
|
// ErrTooManyRows occurs when more rows than expected are returned.
|
||||||
|
ErrTooManyRows = errors.New("too many rows in result set")
|
||||||
|
)
|
||||||
|
|
||||||
var errDisabledStatementCache = fmt.Errorf("cannot use QueryExecModeCacheStatement with disabled statement cache")
|
var errDisabledStatementCache = fmt.Errorf("cannot use QueryExecModeCacheStatement with disabled statement cache")
|
||||||
var errDisabledDescriptionCache = fmt.Errorf("cannot use QueryExecModeCacheDescribe with disabled description cache")
|
var errDisabledDescriptionCache = fmt.Errorf("cannot use QueryExecModeCacheDescribe with disabled description cache")
|
||||||
|
@ -269,7 +275,7 @@ func connect(ctx context.Context, config *ConnConfig) (c *Conn, err error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes a connection. It is safe to call Close on a already closed
|
// Close closes a connection. It is safe to call Close on an already closed
|
||||||
// connection.
|
// connection.
|
||||||
func (c *Conn) Close(ctx context.Context) error {
|
func (c *Conn) Close(ctx context.Context) error {
|
||||||
if c.IsClosed() {
|
if c.IsClosed() {
|
||||||
|
@ -280,12 +286,15 @@ func (c *Conn) Close(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare creates a prepared statement with name and sql. sql can contain placeholders
|
// Prepare creates a prepared statement with name and sql. sql can contain placeholders for bound parameters. These
|
||||||
// for bound parameters. These placeholders are referenced positional as $1, $2, etc.
|
// placeholders are referenced positionally as $1, $2, etc. name can be used instead of sql with Query, QueryRow, and
|
||||||
|
// Exec to execute the statement. It can also be used with Batch.Queue.
|
||||||
//
|
//
|
||||||
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same
|
// The underlying PostgreSQL identifier for the prepared statement will be name if name != sql or a digest of sql if
|
||||||
// name and sql arguments. This allows a code path to Prepare and Query/Exec without
|
// name == sql.
|
||||||
// concern for if the statement has already been prepared.
|
//
|
||||||
|
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This
|
||||||
|
// allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared.
|
||||||
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
||||||
if c.prepareTracer != nil {
|
if c.prepareTracer != nil {
|
||||||
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
||||||
|
@ -307,22 +316,38 @@ func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.Statem
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
sd, err = c.pgConn.Prepare(ctx, name, sql, nil)
|
var psName, psKey string
|
||||||
|
if name == sql {
|
||||||
|
digest := sha256.Sum256([]byte(sql))
|
||||||
|
psName = "stmt_" + hex.EncodeToString(digest[0:24])
|
||||||
|
psKey = sql
|
||||||
|
} else {
|
||||||
|
psName = name
|
||||||
|
psKey = name
|
||||||
|
}
|
||||||
|
|
||||||
|
sd, err = c.pgConn.Prepare(ctx, psName, sql, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != "" {
|
if psKey != "" {
|
||||||
c.preparedStatements[name] = sd
|
c.preparedStatements[psKey] = sd
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd, nil
|
return sd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocate released a prepared statement
|
// Deallocate releases a prepared statement.
|
||||||
func (c *Conn) Deallocate(ctx context.Context, name string) error {
|
func (c *Conn) Deallocate(ctx context.Context, name string) error {
|
||||||
delete(c.preparedStatements, name)
|
var psName string
|
||||||
_, err := c.pgConn.Exec(ctx, "deallocate "+quoteIdentifier(name)).ReadAll()
|
if sd, ok := c.preparedStatements[name]; ok {
|
||||||
|
delete(c.preparedStatements, name)
|
||||||
|
psName = sd.Name
|
||||||
|
} else {
|
||||||
|
psName = name
|
||||||
|
}
|
||||||
|
_, err := c.pgConn.Exec(ctx, "deallocate "+quoteIdentifier(psName)).ReadAll()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +486,7 @@ func (c *Conn) exec(ctx context.Context, sql string, arguments ...any) (commandT
|
||||||
}
|
}
|
||||||
sd := c.statementCache.Get(sql)
|
sd := c.statementCache.Get(sql)
|
||||||
if sd == nil {
|
if sd == nil {
|
||||||
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pgconn.CommandTag{}, err
|
return pgconn.CommandTag{}, err
|
||||||
}
|
}
|
||||||
|
@ -573,13 +598,16 @@ func (c *Conn) getRows(ctx context.Context, sql string, args []any) *baseRows {
|
||||||
const (
|
const (
|
||||||
_ QueryExecMode = iota
|
_ QueryExecMode = iota
|
||||||
|
|
||||||
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single
|
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single round
|
||||||
// round trip after the statement is cached. This is the default.
|
// trip after the statement is cached. This is the default. If the database schema is modified or the search_path is
|
||||||
|
// changed after a statement is cached then the first execution of a previously cached query may fail. e.g. If the
|
||||||
|
// number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
||||||
QueryExecModeCacheStatement
|
QueryExecModeCacheStatement
|
||||||
|
|
||||||
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the
|
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the extended
|
||||||
// extended protocol. Queries are executed in a single round trip after the description is cached. If the database
|
// protocol. Queries are executed in a single round trip after the description is cached. If the database schema is
|
||||||
// schema is modified or the search_path is changed this may result in undetected result decoding errors.
|
// modified or the search_path is changed after a statement is cached then the first execution of a previously cached
|
||||||
|
// query may fail. e.g. If the number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
||||||
QueryExecModeCacheDescribe
|
QueryExecModeCacheDescribe
|
||||||
|
|
||||||
// Get the statement description on every execution. This uses the extended protocol. Queries require two round trips
|
// Get the statement description on every execution. This uses the extended protocol. Queries require two round trips
|
||||||
|
@ -592,13 +620,13 @@ func (c *Conn) getRows(ctx context.Context, sql string, args []any) *baseRows {
|
||||||
// Assume the PostgreSQL query parameter types based on the Go type of the arguments. This uses the extended protocol
|
// Assume the PostgreSQL query parameter types based on the Go type of the arguments. This uses the extended protocol
|
||||||
// with text formatted parameters and results. Queries are executed in a single round trip. Type mappings can be
|
// with text formatted parameters and results. Queries are executed in a single round trip. Type mappings can be
|
||||||
// registered with pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are
|
// registered with pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are
|
||||||
// unregistered or ambigious. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
// unregistered or ambiguous. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
||||||
// the PostgreSQL type can use a map[string]string directly as an argument. This mode cannot.
|
// the PostgreSQL type can use a map[string]string directly as an argument. This mode cannot.
|
||||||
QueryExecModeExec
|
QueryExecModeExec
|
||||||
|
|
||||||
// Use the simple protocol. Assume the PostgreSQL query parameter types based on the Go type of the arguments.
|
// Use the simple protocol. Assume the PostgreSQL query parameter types based on the Go type of the arguments.
|
||||||
// Queries are executed in a single round trip. Type mappings can be registered with
|
// Queries are executed in a single round trip. Type mappings can be registered with
|
||||||
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambigious.
|
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambiguous.
|
||||||
// e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know the PostgreSQL type can use
|
// e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know the PostgreSQL type can use
|
||||||
// a map[string]string directly as an argument. This mode cannot.
|
// a map[string]string directly as an argument. This mode cannot.
|
||||||
//
|
//
|
||||||
|
@ -815,7 +843,7 @@ func (c *Conn) getStatementDescription(
|
||||||
}
|
}
|
||||||
sd = c.statementCache.Get(sql)
|
sd = c.statementCache.Get(sql)
|
||||||
if sd == nil {
|
if sd == nil {
|
||||||
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -994,7 +1022,7 @@ func (c *Conn) sendBatchQueryExecModeCacheStatement(ctx context.Context, b *Batc
|
||||||
bi.sd = distinctNewQueries[idx]
|
bi.sd = distinctNewQueries[idx]
|
||||||
} else {
|
} else {
|
||||||
sd = &pgconn.StatementDescription{
|
sd = &pgconn.StatementDescription{
|
||||||
Name: stmtcache.NextStatementName(),
|
Name: stmtcache.StatementName(bi.query),
|
||||||
SQL: bi.query,
|
SQL: bi.query,
|
||||||
}
|
}
|
||||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||||
|
@ -1062,7 +1090,7 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) {
|
func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) {
|
||||||
pipeline := c.pgConn.StartPipeline(context.Background())
|
pipeline := c.pgConn.StartPipeline(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
if pbr != nil && pbr.err != nil {
|
if pbr != nil && pbr.err != nil {
|
||||||
pipeline.Close()
|
pipeline.Close()
|
||||||
|
|
12
vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go
generated
vendored
12
vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go
generated
vendored
|
@ -34,7 +34,8 @@ func (c *LRUCache) Get(key string) *pgconn.StatementDescription {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache.
|
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
|
||||||
|
// sd.SQL has been invalidated and HandleInvalidated has not been called yet.
|
||||||
func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
||||||
if sd.SQL == "" {
|
if sd.SQL == "" {
|
||||||
panic("cannot store statement description with empty SQL")
|
panic("cannot store statement description with empty SQL")
|
||||||
|
@ -44,6 +45,13 @@ func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
|
||||||
|
for _, invalidSD := range c.invalidStmts {
|
||||||
|
if invalidSD.SQL == sd.SQL {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.l.Len() == c.cap {
|
if c.l.Len() == c.cap {
|
||||||
c.invalidateOldest()
|
c.invalidateOldest()
|
||||||
}
|
}
|
||||||
|
@ -73,6 +81,8 @@ func (c *LRUCache) InvalidateAll() {
|
||||||
c.l = list.New()
|
c.l = list.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleInvalidated returns a slice of all statement descriptions invalidated since the last call to HandleInvalidated.
|
||||||
|
// Typically, the caller will then deallocate them.
|
||||||
func (c *LRUCache) HandleInvalidated() []*pgconn.StatementDescription {
|
func (c *LRUCache) HandleInvalidated() []*pgconn.StatementDescription {
|
||||||
invalidStmts := c.invalidStmts
|
invalidStmts := c.invalidStmts
|
||||||
c.invalidStmts = nil
|
c.invalidStmts = nil
|
||||||
|
|
31
vendor/github.com/jackc/pgx/v5/internal/stmtcache/stmtcache.go
generated
vendored
31
vendor/github.com/jackc/pgx/v5/internal/stmtcache/stmtcache.go
generated
vendored
|
@ -2,18 +2,17 @@
|
||||||
package stmtcache
|
package stmtcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"crypto/sha256"
|
||||||
"sync/atomic"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stmtCounter int64
|
// StatementName returns a statement name that will be stable for sql across multiple connections and program
|
||||||
|
// executions.
|
||||||
// NextStatementName returns a statement name that will be unique for the lifetime of the program.
|
func StatementName(sql string) string {
|
||||||
func NextStatementName() string {
|
digest := sha256.Sum256([]byte(sql))
|
||||||
n := atomic.AddInt64(&stmtCounter, 1)
|
return "stmtcache_" + hex.EncodeToString(digest[0:24])
|
||||||
return "stmtcache_" + strconv.FormatInt(n, 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache caches statement descriptions.
|
// Cache caches statement descriptions.
|
||||||
|
@ -39,19 +38,3 @@ type Cache interface {
|
||||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
// Cap returns the maximum number of cached prepared statement descriptions.
|
||||||
Cap() int
|
Cap() int
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsStatementInvalid(err error) bool {
|
|
||||||
pgErr, ok := err.(*pgconn.PgError)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/jackc/pgx/issues/1162
|
|
||||||
//
|
|
||||||
// We used to look for the message "cached plan must not change result type". However, that message can be localized.
|
|
||||||
// Unfortunately, error code "0A000" - "FEATURE NOT SUPPORTED" is used for many different errors and the only way to
|
|
||||||
// tell the difference is by the message. But all that happens is we clear a statement that we otherwise wouldn't
|
|
||||||
// have so it should be safe.
|
|
||||||
possibleInvalidCachedPlanError := pgErr.Code == "0A000"
|
|
||||||
return possibleInvalidCachedPlanError
|
|
||||||
}
|
|
||||||
|
|
4
vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go
generated
vendored
4
vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go
generated
vendored
|
@ -47,7 +47,7 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive server-first-message payload in a AuthenticationSASLContinue.
|
// Receive server-first-message payload in an AuthenticationSASLContinue.
|
||||||
saslContinue, err := c.rxSASLContinue()
|
saslContinue, err := c.rxSASLContinue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -67,7 +67,7 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive server-final-message payload in a AuthenticationSASLFinal.
|
// Receive server-final-message payload in an AuthenticationSASLFinal.
|
||||||
saslFinal, err := c.rxSASLFinal()
|
saslFinal, err := c.rxSASLFinal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
10
vendor/github.com/jackc/pgx/v5/pgconn/config.go
generated
vendored
10
vendor/github.com/jackc/pgx/v5/pgconn/config.go
generated
vendored
|
@ -809,7 +809,7 @@ func makeConnectTimeoutDialFunc(timeout time.Duration) DialFunc {
|
||||||
return d.DialContext
|
return d.DialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsReadWrite is an ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsReadWrite is a ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=read-write.
|
// target_session_attrs=read-write.
|
||||||
func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
||||||
|
@ -824,7 +824,7 @@ func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgC
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsReadOnly is an ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsReadOnly is a ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=read-only.
|
// target_session_attrs=read-only.
|
||||||
func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
||||||
|
@ -839,7 +839,7 @@ func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgCo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsStandby is an ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsStandby is a ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=standby.
|
// target_session_attrs=standby.
|
||||||
func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
|
@ -854,7 +854,7 @@ func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgCon
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsPrimary is a ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=primary.
|
// target_session_attrs=primary.
|
||||||
func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
|
@ -869,7 +869,7 @@ func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgCon
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsPreferStandby is an ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsPreferStandby is a ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=prefer-standby.
|
// target_session_attrs=prefer-standby.
|
||||||
func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
|
|
59
vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go
generated
vendored
59
vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go
generated
vendored
|
@ -74,6 +74,7 @@ type PgConn struct {
|
||||||
frontend *pgproto3.Frontend
|
frontend *pgproto3.Frontend
|
||||||
bgReader *bgreader.BGReader
|
bgReader *bgreader.BGReader
|
||||||
slowWriteTimer *time.Timer
|
slowWriteTimer *time.Timer
|
||||||
|
bgReaderStarted chan struct{}
|
||||||
|
|
||||||
config *Config
|
config *Config
|
||||||
|
|
||||||
|
@ -301,8 +302,14 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig
|
||||||
pgConn.parameterStatuses = make(map[string]string)
|
pgConn.parameterStatuses = make(map[string]string)
|
||||||
pgConn.status = connStatusConnecting
|
pgConn.status = connStatusConnecting
|
||||||
pgConn.bgReader = bgreader.New(pgConn.conn)
|
pgConn.bgReader = bgreader.New(pgConn.conn)
|
||||||
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64), pgConn.bgReader.Start)
|
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64),
|
||||||
|
func() {
|
||||||
|
pgConn.bgReader.Start()
|
||||||
|
pgConn.bgReaderStarted <- struct{}{}
|
||||||
|
},
|
||||||
|
)
|
||||||
pgConn.slowWriteTimer.Stop()
|
pgConn.slowWriteTimer.Stop()
|
||||||
|
pgConn.bgReaderStarted = make(chan struct{})
|
||||||
pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
||||||
|
|
||||||
startupMsg := pgproto3.StartupMessage{
|
startupMsg := pgproto3.StartupMessage{
|
||||||
|
@ -593,7 +600,7 @@ func (pgConn *PgConn) Frontend() *pgproto3.Frontend {
|
||||||
return pgConn.frontend
|
return pgConn.frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes a connection. It is safe to call Close on a already closed connection. Close attempts a clean close by
|
// Close closes a connection. It is safe to call Close on an already closed connection. Close attempts a clean close by
|
||||||
// sending the exit message to PostgreSQL. However, this could block so ctx is available to limit the time to wait. The
|
// sending the exit message to PostgreSQL. However, this could block so ctx is available to limit the time to wait. The
|
||||||
// underlying net.Conn.Close() will always be called regardless of any other errors.
|
// underlying net.Conn.Close() will always be called regardless of any other errors.
|
||||||
func (pgConn *PgConn) Close(ctx context.Context) error {
|
func (pgConn *PgConn) Close(ctx context.Context) error {
|
||||||
|
@ -935,16 +942,21 @@ func() { cancelConn.SetDeadline(time.Time{}) },
|
||||||
buf := make([]byte, 16)
|
buf := make([]byte, 16)
|
||||||
binary.BigEndian.PutUint32(buf[0:4], 16)
|
binary.BigEndian.PutUint32(buf[0:4], 16)
|
||||||
binary.BigEndian.PutUint32(buf[4:8], 80877102)
|
binary.BigEndian.PutUint32(buf[4:8], 80877102)
|
||||||
binary.BigEndian.PutUint32(buf[8:12], uint32(pgConn.pid))
|
binary.BigEndian.PutUint32(buf[8:12], pgConn.pid)
|
||||||
binary.BigEndian.PutUint32(buf[12:16], uint32(pgConn.secretKey))
|
binary.BigEndian.PutUint32(buf[12:16], pgConn.secretKey)
|
||||||
// Postgres will process the request and close the connection
|
|
||||||
// so when don't need to read the reply
|
if _, err := cancelConn.Write(buf); err != nil {
|
||||||
// https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.6.7.10
|
return fmt.Errorf("write to connection for cancellation: %w", err)
|
||||||
_, err = cancelConn.Write(buf)
|
}
|
||||||
return err
|
|
||||||
|
// Wait for the cancel request to be acknowledged by the server.
|
||||||
|
// It copies the behavior of the libpq: https://github.com/postgres/postgres/blob/REL_16_0/src/interfaces/libpq/fe-connect.c#L4946-L4960
|
||||||
|
_, _ = cancelConn.Read(buf)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForNotification waits for a LISTON/NOTIFY message to be received. It returns an error if a notification was not
|
// WaitForNotification waits for a LISTEN/NOTIFY message to be received. It returns an error if a notification was not
|
||||||
// received.
|
// received.
|
||||||
func (pgConn *PgConn) WaitForNotification(ctx context.Context) error {
|
func (pgConn *PgConn) WaitForNotification(ctx context.Context) error {
|
||||||
if err := pgConn.lock(); err != nil {
|
if err := pgConn.lock(); err != nil {
|
||||||
|
@ -1732,10 +1744,16 @@ func (pgConn *PgConn) enterPotentialWriteReadDeadlock() {
|
||||||
|
|
||||||
// exitPotentialWriteReadDeadlock must be called after a call to enterPotentialWriteReadDeadlock.
|
// exitPotentialWriteReadDeadlock must be called after a call to enterPotentialWriteReadDeadlock.
|
||||||
func (pgConn *PgConn) exitPotentialWriteReadDeadlock() {
|
func (pgConn *PgConn) exitPotentialWriteReadDeadlock() {
|
||||||
// The state of the timer is not relevant upon exiting the potential slow write. It may both
|
if !pgConn.slowWriteTimer.Stop() {
|
||||||
// fire (due to a slow write), or not fire (due to a fast write).
|
// The timer starts its function in a separate goroutine. It is necessary to ensure the background reader has
|
||||||
_ = pgConn.slowWriteTimer.Stop()
|
// started before calling Stop. Otherwise, the background reader may not be stopped. That on its own is not a
|
||||||
pgConn.bgReader.Stop()
|
// serious problem. But what is a serious problem is that the background reader may start at an inopportune time in
|
||||||
|
// a subsequent query. For example, if a subsequent query was canceled then a deadline may be set on the net.Conn to
|
||||||
|
// interrupt an in-progress read. After the read is interrupted, but before the deadline is cleared, the background
|
||||||
|
// reader could start and read a deadline error. Then the next query would receive the an unexpected deadline error.
|
||||||
|
<-pgConn.bgReaderStarted
|
||||||
|
pgConn.bgReader.Stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pgConn *PgConn) flushWithPotentialWriteReadDeadlock() error {
|
func (pgConn *PgConn) flushWithPotentialWriteReadDeadlock() error {
|
||||||
|
@ -1764,7 +1782,7 @@ func (pgConn *PgConn) SyncConn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should never happen. Only way I can imagine this occuring is if the server is constantly sending data such as
|
// This should never happen. Only way I can imagine this occurring is if the server is constantly sending data such as
|
||||||
// LISTEN/NOTIFY or log notifications such that we never can get an empty buffer.
|
// LISTEN/NOTIFY or log notifications such that we never can get an empty buffer.
|
||||||
return errors.New("SyncConn: conn never synchronized")
|
return errors.New("SyncConn: conn never synchronized")
|
||||||
}
|
}
|
||||||
|
@ -1830,8 +1848,14 @@ func Construct(hc *HijackedConn) (*PgConn, error) {
|
||||||
|
|
||||||
pgConn.contextWatcher = newContextWatcher(pgConn.conn)
|
pgConn.contextWatcher = newContextWatcher(pgConn.conn)
|
||||||
pgConn.bgReader = bgreader.New(pgConn.conn)
|
pgConn.bgReader = bgreader.New(pgConn.conn)
|
||||||
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64), pgConn.bgReader.Start)
|
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64),
|
||||||
|
func() {
|
||||||
|
pgConn.bgReader.Start()
|
||||||
|
pgConn.bgReaderStarted <- struct{}{}
|
||||||
|
},
|
||||||
|
)
|
||||||
pgConn.slowWriteTimer.Stop()
|
pgConn.slowWriteTimer.Stop()
|
||||||
|
pgConn.bgReaderStarted = make(chan struct{})
|
||||||
pgConn.frontend = hc.Config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
pgConn.frontend = hc.Config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
||||||
|
|
||||||
return pgConn, nil
|
return pgConn, nil
|
||||||
|
@ -1996,7 +2020,8 @@ func (p *Pipeline) GetResults() (results any, err error) {
|
||||||
for {
|
for {
|
||||||
msg, err := p.conn.receiveMessage()
|
msg, err := p.conn.receiveMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
p.conn.asyncClose()
|
||||||
|
return nil, normalizeTimeoutError(p.ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
|
2
vendor/github.com/jackc/pgx/v5/pgproto3/README.md
generated
vendored
2
vendor/github.com/jackc/pgx/v5/pgproto3/README.md
generated
vendored
|
@ -1,6 +1,6 @@
|
||||||
# pgproto3
|
# pgproto3
|
||||||
|
|
||||||
Package pgproto3 is a encoder and decoder of the PostgreSQL wire protocol version 3.
|
Package pgproto3 is an encoder and decoder of the PostgreSQL wire protocol version 3.
|
||||||
|
|
||||||
pgproto3 can be used as a foundation for PostgreSQL drivers, proxies, mock servers, load balancers and more.
|
pgproto3 can be used as a foundation for PostgreSQL drivers, proxies, mock servers, load balancers and more.
|
||||||
|
|
||||||
|
|
4
vendor/github.com/jackc/pgx/v5/pgproto3/doc.go
generated
vendored
4
vendor/github.com/jackc/pgx/v5/pgproto3/doc.go
generated
vendored
|
@ -1,7 +1,7 @@
|
||||||
// Package pgproto3 is a encoder and decoder of the PostgreSQL wire protocol version 3.
|
// Package pgproto3 is an encoder and decoder of the PostgreSQL wire protocol version 3.
|
||||||
//
|
//
|
||||||
// The primary interfaces are Frontend and Backend. They correspond to a client and server respectively. Messages are
|
// The primary interfaces are Frontend and Backend. They correspond to a client and server respectively. Messages are
|
||||||
// sent with Send (or a specialized Send variant). Messages are automatically bufferred to minimize small writes. Call
|
// sent with Send (or a specialized Send variant). Messages are automatically buffered to minimize small writes. Call
|
||||||
// Flush to ensure a message has actually been sent.
|
// Flush to ensure a message has actually been sent.
|
||||||
//
|
//
|
||||||
// The Trace method of Frontend and Backend can be used to examine the wire-level message traffic. It outputs in a
|
// The Trace method of Frontend and Backend can be used to examine the wire-level message traffic. It outputs in a
|
||||||
|
|
2
vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go
generated
vendored
2
vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go
generated
vendored
|
@ -156,7 +156,7 @@ func (f *Frontend) SendDescribe(msg *Describe) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendExecute sends a Execute message to the backend (i.e. the server). The message is not guaranteed to be written until
|
// SendExecute sends an Execute message to the backend (i.e. the server). The message is not guaranteed to be written until
|
||||||
// Flush is called.
|
// Flush is called.
|
||||||
func (f *Frontend) SendExecute(msg *Execute) {
|
func (f *Frontend) SendExecute(msg *Execute) {
|
||||||
prevLen := len(f.wbuf)
|
prevLen := len(f.wbuf)
|
||||||
|
|
4
vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go
generated
vendored
4
vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go
generated
vendored
|
@ -38,14 +38,14 @@ func (dst *StartupMessage) Decode(src []byte) error {
|
||||||
for {
|
for {
|
||||||
idx := bytes.IndexByte(src[rp:], 0)
|
idx := bytes.IndexByte(src[rp:], 0)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return &invalidMessageFormatErr{messageType: "StartupMesage"}
|
return &invalidMessageFormatErr{messageType: "StartupMessage"}
|
||||||
}
|
}
|
||||||
key := string(src[rp : rp+idx])
|
key := string(src[rp : rp+idx])
|
||||||
rp += idx + 1
|
rp += idx + 1
|
||||||
|
|
||||||
idx = bytes.IndexByte(src[rp:], 0)
|
idx = bytes.IndexByte(src[rp:], 0)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return &invalidMessageFormatErr{messageType: "StartupMesage"}
|
return &invalidMessageFormatErr{messageType: "StartupMessage"}
|
||||||
}
|
}
|
||||||
value := string(src[rp : rp+idx])
|
value := string(src[rp : rp+idx])
|
||||||
rp += idx + 1
|
rp += idx + 1
|
||||||
|
|
4
vendor/github.com/jackc/pgx/v5/pgtype/doc.go
generated
vendored
4
vendor/github.com/jackc/pgx/v5/pgtype/doc.go
generated
vendored
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
Sometimes pgx supports a PostgreSQL type such as numeric but the Go type is in an external package that does not have
|
Sometimes pgx supports a PostgreSQL type such as numeric but the Go type is in an external package that does not have
|
||||||
pgx support such as github.com/shopspring/decimal. These types can be registered with pgtype with custom conversion
|
pgx support such as github.com/shopspring/decimal. These types can be registered with pgtype with custom conversion
|
||||||
logic. See https://github.com/jackc/pgx-shopspring-decimal and https://github.com/jackc/pgx-gofrs-uuid for a example
|
logic. See https://github.com/jackc/pgx-shopspring-decimal and https://github.com/jackc/pgx-gofrs-uuid for example
|
||||||
integrations.
|
integrations.
|
||||||
|
|
||||||
New PostgreSQL Type Support
|
New PostgreSQL Type Support
|
||||||
|
@ -149,7 +149,7 @@ func RegisterDataTypes(ctx context.Context, conn *pgx.Conn) error {
|
||||||
The first step is to use the OID to lookup the correct Codec. If the OID is unavailable, Map will try to find the OID
|
The first step is to use the OID to lookup the correct Codec. If the OID is unavailable, Map will try to find the OID
|
||||||
from previous calls of Map.RegisterDefaultPgType. The Map will call the Codec's PlanScan method to get a plan for
|
from previous calls of Map.RegisterDefaultPgType. The Map will call the Codec's PlanScan method to get a plan for
|
||||||
scanning into the Go value. A Codec will support scanning into one or more Go types. Oftentime these Go types are
|
scanning into the Go value. A Codec will support scanning into one or more Go types. Oftentime these Go types are
|
||||||
interfaces rather than explicit types. For example, PointCodec can use any Go type that implments the PointScanner and
|
interfaces rather than explicit types. For example, PointCodec can use any Go type that implements the PointScanner and
|
||||||
PointValuer interfaces.
|
PointValuer interfaces.
|
||||||
|
|
||||||
If a Go value is not supported directly by a Codec then Map will try wrapping it with additional logic and try again.
|
If a Go value is not supported directly by a Codec then Map will try wrapping it with additional logic and try again.
|
||||||
|
|
24
vendor/github.com/jackc/pgx/v5/pgtype/float4.go
generated
vendored
24
vendor/github.com/jackc/pgx/v5/pgtype/float4.go
generated
vendored
|
@ -3,6 +3,7 @@
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -65,6 +66,29 @@ func (f Float4) Value() (driver.Value, error) {
|
||||||
return float64(f.Float32), nil
|
return float64(f.Float32), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f Float4) MarshalJSON() ([]byte, error) {
|
||||||
|
if !f.Valid {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return json.Marshal(f.Float32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float4) UnmarshalJSON(b []byte) error {
|
||||||
|
var n *float32
|
||||||
|
err := json.Unmarshal(b, &n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
*f = Float4{}
|
||||||
|
} else {
|
||||||
|
*f = Float4{Float32: *n, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Float4Codec struct{}
|
type Float4Codec struct{}
|
||||||
|
|
||||||
func (Float4Codec) FormatSupported(format int16) bool {
|
func (Float4Codec) FormatSupported(format int16) bool {
|
||||||
|
|
30
vendor/github.com/jackc/pgx/v5/pgtype/float8.go
generated
vendored
30
vendor/github.com/jackc/pgx/v5/pgtype/float8.go
generated
vendored
|
@ -74,6 +74,29 @@ func (f Float8) Value() (driver.Value, error) {
|
||||||
return f.Float64, nil
|
return f.Float64, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f Float8) MarshalJSON() ([]byte, error) {
|
||||||
|
if !f.Valid {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return json.Marshal(f.Float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float8) UnmarshalJSON(b []byte) error {
|
||||||
|
var n *float64
|
||||||
|
err := json.Unmarshal(b, &n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
*f = Float8{}
|
||||||
|
} else {
|
||||||
|
*f = Float8{Float64: *n, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Float8Codec struct{}
|
type Float8Codec struct{}
|
||||||
|
|
||||||
func (Float8Codec) FormatSupported(format int16) bool {
|
func (Float8Codec) FormatSupported(format int16) bool {
|
||||||
|
@ -109,13 +132,6 @@ func (Float8Codec) PlanEncode(m *Map, oid uint32, format int16, value any) Encod
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Float8) MarshalJSON() ([]byte, error) {
|
|
||||||
if !f.Valid {
|
|
||||||
return []byte("null"), nil
|
|
||||||
}
|
|
||||||
return json.Marshal(f.Float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
type encodePlanFloat8CodecBinaryFloat64 struct{}
|
type encodePlanFloat8CodecBinaryFloat64 struct{}
|
||||||
|
|
||||||
func (encodePlanFloat8CodecBinaryFloat64) Encode(value any, buf []byte) (newBuf []byte, err error) {
|
func (encodePlanFloat8CodecBinaryFloat64) Encode(value any, buf []byte) (newBuf []byte, err error) {
|
||||||
|
|
2
vendor/github.com/jackc/pgx/v5/pgtype/inet.go
generated
vendored
2
vendor/github.com/jackc/pgx/v5/pgtype/inet.go
generated
vendored
|
@ -156,7 +156,7 @@ func (scanPlanBinaryInetToNetipPrefixScanner) Scan(src []byte, dst any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(src) != 8 && len(src) != 20 {
|
if len(src) != 8 && len(src) != 20 {
|
||||||
return fmt.Errorf("Received an invalid size for a inet: %d", len(src))
|
return fmt.Errorf("Received an invalid size for an inet: %d", len(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore family
|
// ignore family
|
||||||
|
|
16
vendor/github.com/jackc/pgx/v5/pgtype/interval.go
generated
vendored
16
vendor/github.com/jackc/pgx/v5/pgtype/interval.go
generated
vendored
|
@ -179,7 +179,7 @@ func (scanPlanBinaryIntervalToIntervalScanner) Scan(src []byte, dst any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(src) != 16 {
|
if len(src) != 16 {
|
||||||
return fmt.Errorf("Received an invalid size for a interval: %d", len(src))
|
return fmt.Errorf("Received an invalid size for an interval: %d", len(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
microseconds := int64(binary.BigEndian.Uint64(src))
|
microseconds := int64(binary.BigEndian.Uint64(src))
|
||||||
|
@ -242,21 +242,21 @@ func (scanPlanTextAnyToIntervalScanner) Scan(src []byte, dst any) error {
|
||||||
return fmt.Errorf("bad interval minute format: %s", timeParts[1])
|
return fmt.Errorf("bad interval minute format: %s", timeParts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
secondParts := strings.SplitN(timeParts[2], ".", 2)
|
sec, secFrac, secFracFound := strings.Cut(timeParts[2], ".")
|
||||||
|
|
||||||
seconds, err := strconv.ParseInt(secondParts[0], 10, 64)
|
seconds, err := strconv.ParseInt(sec, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad interval second format: %s", secondParts[0])
|
return fmt.Errorf("bad interval second format: %s", sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
var uSeconds int64
|
var uSeconds int64
|
||||||
if len(secondParts) == 2 {
|
if secFracFound {
|
||||||
uSeconds, err = strconv.ParseInt(secondParts[1], 10, 64)
|
uSeconds, err = strconv.ParseInt(secFrac, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad interval decimal format: %s", secondParts[1])
|
return fmt.Errorf("bad interval decimal format: %s", secFrac)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 6-len(secondParts[1]); i++ {
|
for i := 0; i < 6-len(secFrac); i++ {
|
||||||
uSeconds *= 10
|
uSeconds *= 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go
generated
vendored
15
vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go
generated
vendored
|
@ -1358,6 +1358,8 @@ func TryWrapDerefPointerEncodePlan(value any) (plan WrappedEncodePlanNextSetter,
|
||||||
reflect.Bool: reflect.TypeOf(false),
|
reflect.Bool: reflect.TypeOf(false),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var byteSliceType = reflect.TypeOf([]byte{})
|
||||||
|
|
||||||
type underlyingTypeEncodePlan struct {
|
type underlyingTypeEncodePlan struct {
|
||||||
nextValueType reflect.Type
|
nextValueType reflect.Type
|
||||||
next EncodePlan
|
next EncodePlan
|
||||||
|
@ -1372,6 +1374,10 @@ func (plan *underlyingTypeEncodePlan) Encode(value any, buf []byte) (newBuf []by
|
||||||
// TryWrapFindUnderlyingTypeEncodePlan tries to convert to a Go builtin type. e.g. If value was of type MyString and
|
// TryWrapFindUnderlyingTypeEncodePlan tries to convert to a Go builtin type. e.g. If value was of type MyString and
|
||||||
// MyString was defined as a string then a wrapper plan would be returned that converts MyString to string.
|
// MyString was defined as a string then a wrapper plan would be returned that converts MyString to string.
|
||||||
func TryWrapFindUnderlyingTypeEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextValue any, ok bool) {
|
func TryWrapFindUnderlyingTypeEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextValue any, ok bool) {
|
||||||
|
if value == nil {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := value.(driver.Valuer); ok {
|
if _, ok := value.(driver.Valuer); ok {
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
@ -1387,6 +1393,15 @@ func TryWrapFindUnderlyingTypeEncodePlan(value any) (plan WrappedEncodePlanNextS
|
||||||
return &underlyingTypeEncodePlan{nextValueType: nextValueType}, refValue.Convert(nextValueType).Interface(), true
|
return &underlyingTypeEncodePlan{nextValueType: nextValueType}, refValue.Convert(nextValueType).Interface(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// []byte is a special case. It is a slice but we treat it as a scalar type. In the case of a named type like
|
||||||
|
// json.RawMessage which is defined as []byte the underlying type should be considered as []byte. But any other slice
|
||||||
|
// does not have a special underlying type.
|
||||||
|
//
|
||||||
|
// https://github.com/jackc/pgx/issues/1763
|
||||||
|
if refValue.Type() != byteSliceType && refValue.Type().AssignableTo(byteSliceType) {
|
||||||
|
return &underlyingTypeEncodePlan{nextValueType: byteSliceType}, refValue.Convert(byteSliceType).Interface(), true
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
vendor/github.com/jackc/pgx/v5/pgtype/point.go
generated
vendored
16
vendor/github.com/jackc/pgx/v5/pgtype/point.go
generated
vendored
|
@ -50,17 +50,17 @@ func parsePoint(src []byte) (*Point, error) {
|
||||||
if src[0] == '"' && src[len(src)-1] == '"' {
|
if src[0] == '"' && src[len(src)-1] == '"' {
|
||||||
src = src[1 : len(src)-1]
|
src = src[1 : len(src)-1]
|
||||||
}
|
}
|
||||||
parts := strings.SplitN(string(src[1:len(src)-1]), ",", 2)
|
sx, sy, found := strings.Cut(string(src[1:len(src)-1]), ",")
|
||||||
if len(parts) < 2 {
|
if !found {
|
||||||
return nil, fmt.Errorf("invalid format for point")
|
return nil, fmt.Errorf("invalid format for point")
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := strconv.ParseFloat(parts[0], 64)
|
x, err := strconv.ParseFloat(sx, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
y, err := strconv.ParseFloat(parts[1], 64)
|
y, err := strconv.ParseFloat(sy, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -247,17 +247,17 @@ func (scanPlanTextAnyToPointScanner) Scan(src []byte, dst any) error {
|
||||||
return fmt.Errorf("invalid length for point: %v", len(src))
|
return fmt.Errorf("invalid length for point: %v", len(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(string(src[1:len(src)-1]), ",", 2)
|
sx, sy, found := strings.Cut(string(src[1:len(src)-1]), ",")
|
||||||
if len(parts) < 2 {
|
if !found {
|
||||||
return fmt.Errorf("invalid format for point")
|
return fmt.Errorf("invalid format for point")
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := strconv.ParseFloat(parts[0], 64)
|
x, err := strconv.ParseFloat(sx, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
y, err := strconv.ParseFloat(parts[1], 64)
|
y, err := strconv.ParseFloat(sy, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
8
vendor/github.com/jackc/pgx/v5/pgtype/tid.go
generated
vendored
8
vendor/github.com/jackc/pgx/v5/pgtype/tid.go
generated
vendored
|
@ -205,17 +205,17 @@ func (scanPlanTextAnyToTIDScanner) Scan(src []byte, dst any) error {
|
||||||
return fmt.Errorf("invalid length for tid: %v", len(src))
|
return fmt.Errorf("invalid length for tid: %v", len(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(string(src[1:len(src)-1]), ",", 2)
|
block, offset, found := strings.Cut(string(src[1:len(src)-1]), ",")
|
||||||
if len(parts) < 2 {
|
if !found {
|
||||||
return fmt.Errorf("invalid format for tid")
|
return fmt.Errorf("invalid format for tid")
|
||||||
}
|
}
|
||||||
|
|
||||||
blockNumber, err := strconv.ParseUint(parts[0], 10, 32)
|
blockNumber, err := strconv.ParseUint(block, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetNumber, err := strconv.ParseUint(parts[1], 10, 16)
|
offsetNumber, err := strconv.ParseUint(offset, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
52
vendor/github.com/jackc/pgx/v5/pgxpool/batch_results.go
generated
vendored
Normal file
52
vendor/github.com/jackc/pgx/v5/pgxpool/batch_results.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errBatchResults struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br errBatchResults) Exec() (pgconn.CommandTag, error) {
|
||||||
|
return pgconn.CommandTag{}, br.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br errBatchResults) Query() (pgx.Rows, error) {
|
||||||
|
return errRows{err: br.err}, br.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br errBatchResults) QueryRow() pgx.Row {
|
||||||
|
return errRow{err: br.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br errBatchResults) Close() error {
|
||||||
|
return br.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolBatchResults struct {
|
||||||
|
br pgx.BatchResults
|
||||||
|
c *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *poolBatchResults) Exec() (pgconn.CommandTag, error) {
|
||||||
|
return br.br.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *poolBatchResults) Query() (pgx.Rows, error) {
|
||||||
|
return br.br.Query()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *poolBatchResults) QueryRow() pgx.Row {
|
||||||
|
return br.br.QueryRow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *poolBatchResults) Close() error {
|
||||||
|
err := br.br.Close()
|
||||||
|
if br.c != nil {
|
||||||
|
br.c.Release()
|
||||||
|
br.c = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
130
vendor/github.com/jackc/pgx/v5/pgxpool/conn.go
generated
vendored
Normal file
130
vendor/github.com/jackc/pgx/v5/pgxpool/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"github.com/jackc/puddle/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn is an acquired *pgx.Conn from a Pool.
|
||||||
|
type Conn struct {
|
||||||
|
res *puddle.Resource[*connResource]
|
||||||
|
p *Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release returns c to the pool it was acquired from. Once Release has been called, other methods must not be called.
|
||||||
|
// However, it is safe to call Release multiple times. Subsequent calls after the first will be ignored.
|
||||||
|
func (c *Conn) Release() {
|
||||||
|
if c.res == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := c.Conn()
|
||||||
|
res := c.res
|
||||||
|
c.res = nil
|
||||||
|
|
||||||
|
if conn.IsClosed() || conn.PgConn().IsBusy() || conn.PgConn().TxStatus() != 'I' {
|
||||||
|
res.Destroy()
|
||||||
|
// Signal to the health check to run since we just destroyed a connections
|
||||||
|
// and we might be below minConns now
|
||||||
|
c.p.triggerHealthCheck()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pool is consistently being used, we might never get to check the
|
||||||
|
// lifetime of a connection since we only check idle connections in checkConnsHealth
|
||||||
|
// so we also check the lifetime here and force a health check
|
||||||
|
if c.p.isExpired(res) {
|
||||||
|
atomic.AddInt64(&c.p.lifetimeDestroyCount, 1)
|
||||||
|
res.Destroy()
|
||||||
|
// Signal to the health check to run since we just destroyed a connections
|
||||||
|
// and we might be below minConns now
|
||||||
|
c.p.triggerHealthCheck()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.p.afterRelease == nil {
|
||||||
|
res.Release()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if c.p.afterRelease(conn) {
|
||||||
|
res.Release()
|
||||||
|
} else {
|
||||||
|
res.Destroy()
|
||||||
|
// Signal to the health check to run since we just destroyed a connections
|
||||||
|
// and we might be below minConns now
|
||||||
|
c.p.triggerHealthCheck()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack assumes ownership of the connection from the pool. Caller is responsible for closing the connection. Hijack
|
||||||
|
// will panic if called on an already released or hijacked connection.
|
||||||
|
func (c *Conn) Hijack() *pgx.Conn {
|
||||||
|
if c.res == nil {
|
||||||
|
panic("cannot hijack already released or hijacked connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := c.Conn()
|
||||||
|
res := c.res
|
||||||
|
c.res = nil
|
||||||
|
|
||||||
|
res.Hijack()
|
||||||
|
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) {
|
||||||
|
return c.Conn().Exec(ctx, sql, arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
||||||
|
return c.Conn().Query(ctx, sql, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
|
||||||
|
return c.Conn().QueryRow(ctx, sql, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults {
|
||||||
|
return c.Conn().SendBatch(ctx, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) {
|
||||||
|
return c.Conn().CopyFrom(ctx, tableName, columnNames, rowSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin starts a transaction block from the *Conn without explicitly setting a transaction mode (see BeginTx with TxOptions if transaction mode is required).
|
||||||
|
func (c *Conn) Begin(ctx context.Context) (pgx.Tx, error) {
|
||||||
|
return c.Conn().Begin(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx starts a transaction block from the *Conn with txOptions determining the transaction mode.
|
||||||
|
func (c *Conn) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) {
|
||||||
|
return c.Conn().BeginTx(ctx, txOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Ping(ctx context.Context) error {
|
||||||
|
return c.Conn().Ping(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Conn() *pgx.Conn {
|
||||||
|
return c.connResource().conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) connResource() *connResource {
|
||||||
|
return c.res.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) getPoolRow(r pgx.Row) *poolRow {
|
||||||
|
return c.connResource().getPoolRow(c, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) getPoolRows(r pgx.Rows) *poolRows {
|
||||||
|
return c.connResource().getPoolRows(c, r)
|
||||||
|
}
|
27
vendor/github.com/jackc/pgx/v5/pgxpool/doc.go
generated
vendored
Normal file
27
vendor/github.com/jackc/pgx/v5/pgxpool/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Package pgxpool is a concurrency-safe connection pool for pgx.
|
||||||
|
/*
|
||||||
|
pgxpool implements a nearly identical interface to pgx connections.
|
||||||
|
|
||||||
|
Creating a Pool
|
||||||
|
|
||||||
|
The primary way of creating a pool is with [pgxpool.New]:
|
||||||
|
|
||||||
|
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
|
||||||
|
|
||||||
|
The database connection string can be in URL or DSN format. PostgreSQL settings, pgx settings, and pool settings can be
|
||||||
|
specified here. In addition, a config struct can be created by [ParseConfig].
|
||||||
|
|
||||||
|
config, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||||
|
// do something with every new connection
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := pgxpool.NewWithConfig(context.Background(), config)
|
||||||
|
|
||||||
|
A pool returns without waiting for any connections to be established. Acquire a connection immediately after creating
|
||||||
|
the pool to check if a connection can successfully be established.
|
||||||
|
*/
|
||||||
|
package pgxpool
|
695
vendor/github.com/jackc/pgx/v5/pgxpool/pool.go
generated
vendored
Normal file
695
vendor/github.com/jackc/pgx/v5/pgxpool/pool.go
generated
vendored
Normal file
|
@ -0,0 +1,695 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"github.com/jackc/puddle/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultMaxConns = int32(4)
|
||||||
|
var defaultMinConns = int32(0)
|
||||||
|
var defaultMaxConnLifetime = time.Hour
|
||||||
|
var defaultMaxConnIdleTime = time.Minute * 30
|
||||||
|
var defaultHealthCheckPeriod = time.Minute
|
||||||
|
|
||||||
|
type connResource struct {
|
||||||
|
conn *pgx.Conn
|
||||||
|
conns []Conn
|
||||||
|
poolRows []poolRow
|
||||||
|
poolRowss []poolRows
|
||||||
|
maxAgeTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *connResource) getConn(p *Pool, res *puddle.Resource[*connResource]) *Conn {
|
||||||
|
if len(cr.conns) == 0 {
|
||||||
|
cr.conns = make([]Conn, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &cr.conns[len(cr.conns)-1]
|
||||||
|
cr.conns = cr.conns[0 : len(cr.conns)-1]
|
||||||
|
|
||||||
|
c.res = res
|
||||||
|
c.p = p
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *connResource) getPoolRow(c *Conn, r pgx.Row) *poolRow {
|
||||||
|
if len(cr.poolRows) == 0 {
|
||||||
|
cr.poolRows = make([]poolRow, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr := &cr.poolRows[len(cr.poolRows)-1]
|
||||||
|
cr.poolRows = cr.poolRows[0 : len(cr.poolRows)-1]
|
||||||
|
|
||||||
|
pr.c = c
|
||||||
|
pr.r = r
|
||||||
|
|
||||||
|
return pr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *connResource) getPoolRows(c *Conn, r pgx.Rows) *poolRows {
|
||||||
|
if len(cr.poolRowss) == 0 {
|
||||||
|
cr.poolRowss = make([]poolRows, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr := &cr.poolRowss[len(cr.poolRowss)-1]
|
||||||
|
cr.poolRowss = cr.poolRowss[0 : len(cr.poolRowss)-1]
|
||||||
|
|
||||||
|
pr.c = c
|
||||||
|
pr.r = r
|
||||||
|
|
||||||
|
return pr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool allows for connection reuse.
|
||||||
|
type Pool struct {
|
||||||
|
// 64 bit fields accessed with atomics must be at beginning of struct to guarantee alignment for certain 32-bit
|
||||||
|
// architectures. See BUGS section of https://pkg.go.dev/sync/atomic and https://github.com/jackc/pgx/issues/1288.
|
||||||
|
newConnsCount int64
|
||||||
|
lifetimeDestroyCount int64
|
||||||
|
idleDestroyCount int64
|
||||||
|
|
||||||
|
p *puddle.Pool[*connResource]
|
||||||
|
config *Config
|
||||||
|
beforeConnect func(context.Context, *pgx.ConnConfig) error
|
||||||
|
afterConnect func(context.Context, *pgx.Conn) error
|
||||||
|
beforeAcquire func(context.Context, *pgx.Conn) bool
|
||||||
|
afterRelease func(*pgx.Conn) bool
|
||||||
|
beforeClose func(*pgx.Conn)
|
||||||
|
minConns int32
|
||||||
|
maxConns int32
|
||||||
|
maxConnLifetime time.Duration
|
||||||
|
maxConnLifetimeJitter time.Duration
|
||||||
|
maxConnIdleTime time.Duration
|
||||||
|
healthCheckPeriod time.Duration
|
||||||
|
|
||||||
|
healthCheckChan chan struct{}
|
||||||
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
closeChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration struct for creating a pool. It must be created by [ParseConfig] and then it can be
|
||||||
|
// modified.
|
||||||
|
type Config struct {
|
||||||
|
ConnConfig *pgx.ConnConfig
|
||||||
|
|
||||||
|
// BeforeConnect is called before a new connection is made. It is passed a copy of the underlying pgx.ConnConfig and
|
||||||
|
// will not impact any existing open connections.
|
||||||
|
BeforeConnect func(context.Context, *pgx.ConnConfig) error
|
||||||
|
|
||||||
|
// AfterConnect is called after a connection is established, but before it is added to the pool.
|
||||||
|
AfterConnect func(context.Context, *pgx.Conn) error
|
||||||
|
|
||||||
|
// BeforeAcquire is called before a connection is acquired from the pool. It must return true to allow the
|
||||||
|
// acquisition or false to indicate that the connection should be destroyed and a different connection should be
|
||||||
|
// acquired.
|
||||||
|
BeforeAcquire func(context.Context, *pgx.Conn) bool
|
||||||
|
|
||||||
|
// AfterRelease is called after a connection is released, but before it is returned to the pool. It must return true to
|
||||||
|
// return the connection to the pool or false to destroy the connection.
|
||||||
|
AfterRelease func(*pgx.Conn) bool
|
||||||
|
|
||||||
|
// BeforeClose is called right before a connection is closed and removed from the pool.
|
||||||
|
BeforeClose func(*pgx.Conn)
|
||||||
|
|
||||||
|
// MaxConnLifetime is the duration since creation after which a connection will be automatically closed.
|
||||||
|
MaxConnLifetime time.Duration
|
||||||
|
|
||||||
|
// MaxConnLifetimeJitter is the duration after MaxConnLifetime to randomly decide to close a connection.
|
||||||
|
// This helps prevent all connections from being closed at the exact same time, starving the pool.
|
||||||
|
MaxConnLifetimeJitter time.Duration
|
||||||
|
|
||||||
|
// MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check.
|
||||||
|
MaxConnIdleTime time.Duration
|
||||||
|
|
||||||
|
// MaxConns is the maximum size of the pool. The default is the greater of 4 or runtime.NumCPU().
|
||||||
|
MaxConns int32
|
||||||
|
|
||||||
|
// MinConns is the minimum size of the pool. After connection closes, the pool might dip below MinConns. A low
|
||||||
|
// number of MinConns might mean the pool is empty after MaxConnLifetime until the health check has a chance
|
||||||
|
// to create new connections.
|
||||||
|
MinConns int32
|
||||||
|
|
||||||
|
// HealthCheckPeriod is the duration between checks of the health of idle connections.
|
||||||
|
HealthCheckPeriod time.Duration
|
||||||
|
|
||||||
|
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep copy of the config that is safe to use and modify.
|
||||||
|
// The only exception is the tls.Config:
|
||||||
|
// according to the tls.Config docs it must not be modified after creation.
|
||||||
|
func (c *Config) Copy() *Config {
|
||||||
|
newConfig := new(Config)
|
||||||
|
*newConfig = *c
|
||||||
|
newConfig.ConnConfig = c.ConnConfig.Copy()
|
||||||
|
return newConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnString returns the connection string as parsed by pgxpool.ParseConfig into pgxpool.Config.
|
||||||
|
func (c *Config) ConnString() string { return c.ConnConfig.ConnString() }
|
||||||
|
|
||||||
|
// New creates a new Pool. See [ParseConfig] for information on connString format.
|
||||||
|
func New(ctx context.Context, connString string) (*Pool, error) {
|
||||||
|
config, err := ParseConfig(connString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewWithConfig(ctx, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithConfig creates a new Pool. config must have been created by [ParseConfig].
|
||||||
|
func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) {
|
||||||
|
// Default values are set in ParseConfig. Enforce initial creation by ParseConfig rather than setting defaults from
|
||||||
|
// zero values.
|
||||||
|
if !config.createdByParseConfig {
|
||||||
|
panic("config must be created by ParseConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Pool{
|
||||||
|
config: config,
|
||||||
|
beforeConnect: config.BeforeConnect,
|
||||||
|
afterConnect: config.AfterConnect,
|
||||||
|
beforeAcquire: config.BeforeAcquire,
|
||||||
|
afterRelease: config.AfterRelease,
|
||||||
|
beforeClose: config.BeforeClose,
|
||||||
|
minConns: config.MinConns,
|
||||||
|
maxConns: config.MaxConns,
|
||||||
|
maxConnLifetime: config.MaxConnLifetime,
|
||||||
|
maxConnLifetimeJitter: config.MaxConnLifetimeJitter,
|
||||||
|
maxConnIdleTime: config.MaxConnIdleTime,
|
||||||
|
healthCheckPeriod: config.HealthCheckPeriod,
|
||||||
|
healthCheckChan: make(chan struct{}, 1),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.p, err = puddle.NewPool(
|
||||||
|
&puddle.Config[*connResource]{
|
||||||
|
Constructor: func(ctx context.Context) (*connResource, error) {
|
||||||
|
atomic.AddInt64(&p.newConnsCount, 1)
|
||||||
|
connConfig := p.config.ConnConfig.Copy()
|
||||||
|
|
||||||
|
// Connection will continue in background even if Acquire is canceled. Ensure that a connect won't hang forever.
|
||||||
|
if connConfig.ConnectTimeout <= 0 {
|
||||||
|
connConfig.ConnectTimeout = 2 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.beforeConnect != nil {
|
||||||
|
if err := p.beforeConnect(ctx, connConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := pgx.ConnectConfig(ctx, connConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.afterConnect != nil {
|
||||||
|
err = p.afterConnect(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close(ctx)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jitterSecs := rand.Float64() * config.MaxConnLifetimeJitter.Seconds()
|
||||||
|
maxAgeTime := time.Now().Add(config.MaxConnLifetime).Add(time.Duration(jitterSecs) * time.Second)
|
||||||
|
|
||||||
|
cr := &connResource{
|
||||||
|
conn: conn,
|
||||||
|
conns: make([]Conn, 64),
|
||||||
|
poolRows: make([]poolRow, 64),
|
||||||
|
poolRowss: make([]poolRows, 64),
|
||||||
|
maxAgeTime: maxAgeTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cr, nil
|
||||||
|
},
|
||||||
|
Destructor: func(value *connResource) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
conn := value.conn
|
||||||
|
if p.beforeClose != nil {
|
||||||
|
p.beforeClose(conn)
|
||||||
|
}
|
||||||
|
conn.Close(ctx)
|
||||||
|
select {
|
||||||
|
case <-conn.PgConn().CleanupDone():
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
},
|
||||||
|
MaxSize: config.MaxConns,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
p.createIdleResources(ctx, int(p.minConns))
|
||||||
|
p.backgroundHealthCheck()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseConfig builds a Config from connString. It parses connString with the same behavior as [pgx.ParseConfig] with the
|
||||||
|
// addition of the following variables:
|
||||||
|
//
|
||||||
|
// - pool_max_conns: integer greater than 0
|
||||||
|
// - pool_min_conns: integer 0 or greater
|
||||||
|
// - pool_max_conn_lifetime: duration string
|
||||||
|
// - pool_max_conn_idle_time: duration string
|
||||||
|
// - pool_health_check_period: duration string
|
||||||
|
// - pool_max_conn_lifetime_jitter: duration string
|
||||||
|
//
|
||||||
|
// 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 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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &Config{
|
||||||
|
ConnConfig: connConfig,
|
||||||
|
createdByParseConfig: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_max_conns"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_max_conns")
|
||||||
|
n, err := strconv.ParseInt(s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse pool_max_conns: %w", err)
|
||||||
|
}
|
||||||
|
if n < 1 {
|
||||||
|
return nil, fmt.Errorf("pool_max_conns too small: %d", n)
|
||||||
|
}
|
||||||
|
config.MaxConns = int32(n)
|
||||||
|
} else {
|
||||||
|
config.MaxConns = defaultMaxConns
|
||||||
|
if numCPU := int32(runtime.NumCPU()); numCPU > config.MaxConns {
|
||||||
|
config.MaxConns = numCPU
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_min_conns"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_min_conns")
|
||||||
|
n, err := strconv.ParseInt(s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse pool_min_conns: %w", err)
|
||||||
|
}
|
||||||
|
config.MinConns = int32(n)
|
||||||
|
} else {
|
||||||
|
config.MinConns = defaultMinConns
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_max_conn_lifetime"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_max_conn_lifetime")
|
||||||
|
d, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid pool_max_conn_lifetime: %w", err)
|
||||||
|
}
|
||||||
|
config.MaxConnLifetime = d
|
||||||
|
} else {
|
||||||
|
config.MaxConnLifetime = defaultMaxConnLifetime
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_max_conn_idle_time"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_max_conn_idle_time")
|
||||||
|
d, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid pool_max_conn_idle_time: %w", err)
|
||||||
|
}
|
||||||
|
config.MaxConnIdleTime = d
|
||||||
|
} else {
|
||||||
|
config.MaxConnIdleTime = defaultMaxConnIdleTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_health_check_period"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_health_check_period")
|
||||||
|
d, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid pool_health_check_period: %w", err)
|
||||||
|
}
|
||||||
|
config.HealthCheckPeriod = d
|
||||||
|
} else {
|
||||||
|
config.HealthCheckPeriod = defaultHealthCheckPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.ConnConfig.Config.RuntimeParams["pool_max_conn_lifetime_jitter"]; ok {
|
||||||
|
delete(connConfig.Config.RuntimeParams, "pool_max_conn_lifetime_jitter")
|
||||||
|
d, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid pool_max_conn_lifetime_jitter: %w", err)
|
||||||
|
}
|
||||||
|
config.MaxConnLifetimeJitter = d
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all connections in the pool and rejects future Acquire calls. Blocks until all connections are returned
|
||||||
|
// to pool and closed.
|
||||||
|
func (p *Pool) Close() {
|
||||||
|
p.closeOnce.Do(func() {
|
||||||
|
close(p.closeChan)
|
||||||
|
p.p.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) isExpired(res *puddle.Resource[*connResource]) bool {
|
||||||
|
return time.Now().After(res.Value().maxAgeTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) triggerHealthCheck() {
|
||||||
|
go func() {
|
||||||
|
// Destroy is asynchronous so we give it time to actually remove itself from
|
||||||
|
// the pool otherwise we might try to check the pool size too soon
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
select {
|
||||||
|
case p.healthCheckChan <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) backgroundHealthCheck() {
|
||||||
|
ticker := time.NewTicker(p.healthCheckPeriod)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.closeChan:
|
||||||
|
return
|
||||||
|
case <-p.healthCheckChan:
|
||||||
|
p.checkHealth()
|
||||||
|
case <-ticker.C:
|
||||||
|
p.checkHealth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) checkHealth() {
|
||||||
|
for {
|
||||||
|
// If checkMinConns failed we don't destroy any connections since we couldn't
|
||||||
|
// even get to minConns
|
||||||
|
if err := p.checkMinConns(); err != nil {
|
||||||
|
// Should we log this error somewhere?
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !p.checkConnsHealth() {
|
||||||
|
// Since we didn't destroy any connections we can stop looping
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Technically Destroy is asynchronous but 500ms should be enough for it to
|
||||||
|
// remove it from the underlying pool
|
||||||
|
select {
|
||||||
|
case <-p.closeChan:
|
||||||
|
return
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConnsHealth will check all idle connections, destroy a connection if
|
||||||
|
// it's idle or too old, and returns true if any were destroyed
|
||||||
|
func (p *Pool) checkConnsHealth() bool {
|
||||||
|
var destroyed bool
|
||||||
|
totalConns := p.Stat().TotalConns()
|
||||||
|
resources := p.p.AcquireAllIdle()
|
||||||
|
for _, res := range resources {
|
||||||
|
// We're okay going under minConns if the lifetime is up
|
||||||
|
if p.isExpired(res) && totalConns >= p.minConns {
|
||||||
|
atomic.AddInt64(&p.lifetimeDestroyCount, 1)
|
||||||
|
res.Destroy()
|
||||||
|
destroyed = true
|
||||||
|
// Since Destroy is async we manually decrement totalConns.
|
||||||
|
totalConns--
|
||||||
|
} else if res.IdleDuration() > p.maxConnIdleTime && totalConns > p.minConns {
|
||||||
|
atomic.AddInt64(&p.idleDestroyCount, 1)
|
||||||
|
res.Destroy()
|
||||||
|
destroyed = true
|
||||||
|
// Since Destroy is async we manually decrement totalConns.
|
||||||
|
totalConns--
|
||||||
|
} else {
|
||||||
|
res.ReleaseUnused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return destroyed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) checkMinConns() error {
|
||||||
|
// TotalConns can include ones that are being destroyed but we should have
|
||||||
|
// sleep(500ms) around all of the destroys to help prevent that from throwing
|
||||||
|
// off this check
|
||||||
|
toCreate := p.minConns - p.Stat().TotalConns()
|
||||||
|
if toCreate > 0 {
|
||||||
|
return p.createIdleResources(context.Background(), int(toCreate))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) createIdleResources(parentCtx context.Context, targetResources int) error {
|
||||||
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
errs := make(chan error, targetResources)
|
||||||
|
|
||||||
|
for i := 0; i < targetResources; i++ {
|
||||||
|
go func() {
|
||||||
|
err := p.p.CreateResource(ctx)
|
||||||
|
// Ignore ErrNotAvailable since it means that the pool has become full since we started creating resource.
|
||||||
|
if err == puddle.ErrNotAvailable {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
errs <- err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstError error
|
||||||
|
for i := 0; i < targetResources; i++ {
|
||||||
|
err := <-errs
|
||||||
|
if err != nil && firstError == nil {
|
||||||
|
cancel()
|
||||||
|
firstError = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire returns a connection (*Conn) from the Pool
|
||||||
|
func (p *Pool) Acquire(ctx context.Context) (*Conn, error) {
|
||||||
|
for {
|
||||||
|
res, err := p.p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := res.Value()
|
||||||
|
|
||||||
|
if res.IdleDuration() > time.Second {
|
||||||
|
err := cr.conn.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
res.Destroy()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.beforeAcquire == nil || p.beforeAcquire(ctx, cr.conn) {
|
||||||
|
return cr.getConn(p, res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireFunc acquires a *Conn and calls f with that *Conn. ctx will only affect the Acquire. It has no effect on the
|
||||||
|
// call of f. The return value is either an error acquiring the *Conn or the return value of f. The *Conn is
|
||||||
|
// automatically released after the call of f.
|
||||||
|
func (p *Pool) AcquireFunc(ctx context.Context, f func(*Conn) error) error {
|
||||||
|
conn, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Release()
|
||||||
|
|
||||||
|
return f(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireAllIdle atomically acquires all currently idle connections. Its intended use is for health check and
|
||||||
|
// keep-alive functionality. It does not update pool statistics.
|
||||||
|
func (p *Pool) AcquireAllIdle(ctx context.Context) []*Conn {
|
||||||
|
resources := p.p.AcquireAllIdle()
|
||||||
|
conns := make([]*Conn, 0, len(resources))
|
||||||
|
for _, res := range resources {
|
||||||
|
cr := res.Value()
|
||||||
|
if p.beforeAcquire == nil || p.beforeAcquire(ctx, cr.conn) {
|
||||||
|
conns = append(conns, cr.getConn(p, res))
|
||||||
|
} else {
|
||||||
|
res.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset closes all connections, but leaves the pool open. It is intended for use when an error is detected that would
|
||||||
|
// disrupt all connections (such as a network interruption or a server state change).
|
||||||
|
//
|
||||||
|
// It is safe to reset a pool while connections are checked out. Those connections will be closed when they are returned
|
||||||
|
// to the pool.
|
||||||
|
func (p *Pool) Reset() {
|
||||||
|
p.p.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns a copy of config that was used to initialize this pool.
|
||||||
|
func (p *Pool) Config() *Config { return p.config.Copy() }
|
||||||
|
|
||||||
|
// Stat returns a pgxpool.Stat struct with a snapshot of Pool statistics.
|
||||||
|
func (p *Pool) Stat() *Stat {
|
||||||
|
return &Stat{
|
||||||
|
s: p.p.Stat(),
|
||||||
|
newConnsCount: atomic.LoadInt64(&p.newConnsCount),
|
||||||
|
lifetimeDestroyCount: atomic.LoadInt64(&p.lifetimeDestroyCount),
|
||||||
|
idleDestroyCount: atomic.LoadInt64(&p.idleDestroyCount),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec acquires a connection from the Pool and executes the given SQL.
|
||||||
|
// SQL can be either a prepared statement name or an SQL string.
|
||||||
|
// Arguments should be referenced positionally from the SQL string as $1, $2, etc.
|
||||||
|
// The acquired connection is returned to the pool when the Exec function returns.
|
||||||
|
func (p *Pool) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return pgconn.CommandTag{}, err
|
||||||
|
}
|
||||||
|
defer c.Release()
|
||||||
|
|
||||||
|
return c.Exec(ctx, sql, arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query acquires a connection and executes a query that returns pgx.Rows.
|
||||||
|
// Arguments should be referenced positionally from the SQL string as $1, $2, etc.
|
||||||
|
// See pgx.Rows documentation to close the returned Rows and return the acquired connection to the Pool.
|
||||||
|
//
|
||||||
|
// If there is an error, the returned pgx.Rows will be returned in an error state.
|
||||||
|
// If preferred, ignore the error returned from Query and handle errors using the returned pgx.Rows.
|
||||||
|
//
|
||||||
|
// For extra control over how the query is executed, the types QuerySimpleProtocol, QueryResultFormats, and
|
||||||
|
// QueryResultFormatsByOID may be used as the first args to control exactly how the query is executed. This is rarely
|
||||||
|
// needed. See the documentation for those types for details.
|
||||||
|
func (p *Pool) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errRows{err: err}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := c.Query(ctx, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
c.Release()
|
||||||
|
return errRows{err: err}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.getPoolRows(rows), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRow acquires a connection and executes a query that is expected
|
||||||
|
// to return at most one row (pgx.Row). Errors are deferred until pgx.Row's
|
||||||
|
// Scan method is called. If the query selects no rows, pgx.Row's Scan will
|
||||||
|
// return ErrNoRows. Otherwise, pgx.Row's Scan scans the first selected row
|
||||||
|
// and discards the rest. The acquired connection is returned to the Pool when
|
||||||
|
// pgx.Row's Scan method is called.
|
||||||
|
//
|
||||||
|
// Arguments should be referenced positionally from the SQL string as $1, $2, etc.
|
||||||
|
//
|
||||||
|
// For extra control over how the query is executed, the types QuerySimpleProtocol, QueryResultFormats, and
|
||||||
|
// QueryResultFormatsByOID may be used as the first args to control exactly how the query is executed. This is rarely
|
||||||
|
// needed. See the documentation for those types for details.
|
||||||
|
func (p *Pool) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errRow{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
row := c.QueryRow(ctx, sql, args...)
|
||||||
|
return c.getPoolRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errBatchResults{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
br := c.SendBatch(ctx, b)
|
||||||
|
return &poolBatchResults{br: br, c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin acquires a connection from the Pool and starts a transaction. Unlike database/sql, the context only affects the begin command. i.e. there is no
|
||||||
|
// auto-rollback on context cancellation. Begin initiates a transaction block without explicitly setting a transaction mode for the block (see BeginTx with TxOptions if transaction mode is required).
|
||||||
|
// *pgxpool.Tx is returned, which implements the pgx.Tx interface.
|
||||||
|
// Commit or Rollback must be called on the returned transaction to finalize the transaction block.
|
||||||
|
func (p *Pool) Begin(ctx context.Context) (pgx.Tx, error) {
|
||||||
|
return p.BeginTx(ctx, pgx.TxOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx acquires a connection from the Pool and starts a transaction with pgx.TxOptions determining the transaction mode.
|
||||||
|
// Unlike database/sql, the context only affects the begin command. i.e. there is no auto-rollback on context cancellation.
|
||||||
|
// *pgxpool.Tx is returned, which implements the pgx.Tx interface.
|
||||||
|
// Commit or Rollback must be called on the returned transaction to finalize the transaction block.
|
||||||
|
func (p *Pool) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := c.BeginTx(ctx, txOptions)
|
||||||
|
if err != nil {
|
||||||
|
c.Release()
|
||||||
|
return nil, 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) {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer c.Release()
|
||||||
|
|
||||||
|
return c.Conn().CopyFrom(ctx, tableName, columnNames, rowSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping acquires a connection from the Pool and executes an empty sql statement against it.
|
||||||
|
// If the sql returns without error, the database Ping is considered successful, otherwise, the error is returned.
|
||||||
|
func (p *Pool) Ping(ctx context.Context) error {
|
||||||
|
c, err := p.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Release()
|
||||||
|
return c.Ping(ctx)
|
||||||
|
}
|
116
vendor/github.com/jackc/pgx/v5/pgxpool/rows.go
generated
vendored
Normal file
116
vendor/github.com/jackc/pgx/v5/pgxpool/rows.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errRows struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errRows) Close() {}
|
||||||
|
func (e errRows) Err() error { return e.err }
|
||||||
|
func (errRows) CommandTag() pgconn.CommandTag { return pgconn.CommandTag{} }
|
||||||
|
func (errRows) FieldDescriptions() []pgconn.FieldDescription { return nil }
|
||||||
|
func (errRows) Next() bool { return false }
|
||||||
|
func (e errRows) Scan(dest ...any) error { return e.err }
|
||||||
|
func (e errRows) Values() ([]any, error) { return nil, e.err }
|
||||||
|
func (e errRows) RawValues() [][]byte { return nil }
|
||||||
|
func (e errRows) Conn() *pgx.Conn { return nil }
|
||||||
|
|
||||||
|
type errRow struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errRow) Scan(dest ...any) error { return e.err }
|
||||||
|
|
||||||
|
type poolRows struct {
|
||||||
|
r pgx.Rows
|
||||||
|
c *Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Close() {
|
||||||
|
rows.r.Close()
|
||||||
|
if rows.c != nil {
|
||||||
|
rows.c.Release()
|
||||||
|
rows.c = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Err() error {
|
||||||
|
if rows.err != nil {
|
||||||
|
return rows.err
|
||||||
|
}
|
||||||
|
return rows.r.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) CommandTag() pgconn.CommandTag {
|
||||||
|
return rows.r.CommandTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) FieldDescriptions() []pgconn.FieldDescription {
|
||||||
|
return rows.r.FieldDescriptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Next() bool {
|
||||||
|
if rows.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
n := rows.r.Next()
|
||||||
|
if !n {
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Scan(dest ...any) error {
|
||||||
|
err := rows.r.Scan(dest...)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Values() ([]any, error) {
|
||||||
|
values, err := rows.r.Values()
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
return values, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) RawValues() [][]byte {
|
||||||
|
return rows.r.RawValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *poolRows) Conn() *pgx.Conn {
|
||||||
|
return rows.r.Conn()
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolRow struct {
|
||||||
|
r pgx.Row
|
||||||
|
c *Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (row *poolRow) Scan(dest ...any) error {
|
||||||
|
if row.err != nil {
|
||||||
|
return row.err
|
||||||
|
}
|
||||||
|
|
||||||
|
panicked := true
|
||||||
|
defer func() {
|
||||||
|
if panicked && row.c != nil {
|
||||||
|
row.c.Release()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err := row.r.Scan(dest...)
|
||||||
|
panicked = false
|
||||||
|
if row.c != nil {
|
||||||
|
row.c.Release()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
84
vendor/github.com/jackc/pgx/v5/pgxpool/stat.go
generated
vendored
Normal file
84
vendor/github.com/jackc/pgx/v5/pgxpool/stat.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/puddle/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stat is a snapshot of Pool statistics.
|
||||||
|
type Stat struct {
|
||||||
|
s *puddle.Stat
|
||||||
|
newConnsCount int64
|
||||||
|
lifetimeDestroyCount int64
|
||||||
|
idleDestroyCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireCount returns the cumulative count of successful acquires from the pool.
|
||||||
|
func (s *Stat) AcquireCount() int64 {
|
||||||
|
return s.s.AcquireCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireDuration returns the total duration of all successful acquires from
|
||||||
|
// the pool.
|
||||||
|
func (s *Stat) AcquireDuration() time.Duration {
|
||||||
|
return s.s.AcquireDuration()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquiredConns returns the number of currently acquired connections in the pool.
|
||||||
|
func (s *Stat) AcquiredConns() int32 {
|
||||||
|
return s.s.AcquiredResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAcquireCount returns the cumulative count of acquires from the pool
|
||||||
|
// that were canceled by a context.
|
||||||
|
func (s *Stat) CanceledAcquireCount() int64 {
|
||||||
|
return s.s.CanceledAcquireCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstructingConns returns the number of conns with construction in progress in
|
||||||
|
// the pool.
|
||||||
|
func (s *Stat) ConstructingConns() int32 {
|
||||||
|
return s.s.ConstructingResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyAcquireCount returns the cumulative count of successful acquires from the pool
|
||||||
|
// that waited for a resource to be released or constructed because the pool was
|
||||||
|
// empty.
|
||||||
|
func (s *Stat) EmptyAcquireCount() int64 {
|
||||||
|
return s.s.EmptyAcquireCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleConns returns the number of currently idle conns in the pool.
|
||||||
|
func (s *Stat) IdleConns() int32 {
|
||||||
|
return s.s.IdleResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxConns returns the maximum size of the pool.
|
||||||
|
func (s *Stat) MaxConns() int32 {
|
||||||
|
return s.s.MaxResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalConns returns the total number of resources currently in the pool.
|
||||||
|
// The value is the sum of ConstructingConns, AcquiredConns, and
|
||||||
|
// IdleConns.
|
||||||
|
func (s *Stat) TotalConns() int32 {
|
||||||
|
return s.s.TotalResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnsCount returns the cumulative count of new connections opened.
|
||||||
|
func (s *Stat) NewConnsCount() int64 {
|
||||||
|
return s.newConnsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLifetimeDestroyCount returns the cumulative count of connections destroyed
|
||||||
|
// because they exceeded MaxConnLifetime.
|
||||||
|
func (s *Stat) MaxLifetimeDestroyCount() int64 {
|
||||||
|
return s.lifetimeDestroyCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxIdleDestroyCount returns the cumulative count of connections destroyed because
|
||||||
|
// they exceeded MaxConnIdleTime.
|
||||||
|
func (s *Stat) MaxIdleDestroyCount() int64 {
|
||||||
|
return s.idleDestroyCount
|
||||||
|
}
|
82
vendor/github.com/jackc/pgx/v5/pgxpool/tx.go
generated
vendored
Normal file
82
vendor/github.com/jackc/pgx/v5/pgxpool/tx.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package pgxpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tx represents a database transaction acquired from a Pool.
|
||||||
|
type Tx struct {
|
||||||
|
t pgx.Tx
|
||||||
|
c *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin starts a pseudo nested transaction implemented with a savepoint.
|
||||||
|
func (tx *Tx) Begin(ctx context.Context) (pgx.Tx, error) {
|
||||||
|
return tx.t.Begin(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit commits the transaction and returns the associated connection back to the Pool. Commit will return ErrTxClosed
|
||||||
|
// if the Tx is already closed, but is otherwise safe to call multiple times. If the commit fails with a rollback status
|
||||||
|
// (e.g. the transaction was already in a broken state) then ErrTxCommitRollback will be returned.
|
||||||
|
func (tx *Tx) Commit(ctx context.Context) error {
|
||||||
|
err := tx.t.Commit(ctx)
|
||||||
|
if tx.c != nil {
|
||||||
|
tx.c.Release()
|
||||||
|
tx.c = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback rolls back the transaction and returns the associated connection back to the Pool. Rollback will return ErrTxClosed
|
||||||
|
// if the Tx is already closed, but is otherwise safe to call multiple times. Hence, defer tx.Rollback() is safe even if
|
||||||
|
// tx.Commit() will be called first in a non-error condition.
|
||||||
|
func (tx *Tx) Rollback(ctx context.Context) error {
|
||||||
|
err := tx.t.Rollback(ctx)
|
||||||
|
if tx.c != nil {
|
||||||
|
tx.c.Release()
|
||||||
|
tx.c = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) {
|
||||||
|
return tx.t.CopyFrom(ctx, tableName, columnNames, rowSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults {
|
||||||
|
return tx.t.SendBatch(ctx, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) LargeObjects() pgx.LargeObjects {
|
||||||
|
return tx.t.LargeObjects()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare creates a prepared statement with name and sql. If the name is empty,
|
||||||
|
// an anonymous prepared statement will be used. sql can contain placeholders
|
||||||
|
// for bound parameters. These placeholders are referenced positionally as $1, $2, etc.
|
||||||
|
//
|
||||||
|
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same
|
||||||
|
// name and sql arguments. This allows a code path to Prepare and Query/Exec without
|
||||||
|
// needing to first check whether the statement has already been prepared.
|
||||||
|
func (tx *Tx) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) {
|
||||||
|
return tx.t.Prepare(ctx, name, sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) {
|
||||||
|
return tx.t.Exec(ctx, sql, arguments...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
||||||
|
return tx.t.Query(ctx, sql, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
|
||||||
|
return tx.t.QueryRow(ctx, sql, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) Conn() *pgx.Conn {
|
||||||
|
return tx.t.Conn()
|
||||||
|
}
|
76
vendor/github.com/jackc/pgx/v5/rows.go
generated
vendored
76
vendor/github.com/jackc/pgx/v5/rows.go
generated
vendored
|
@ -8,7 +8,6 @@
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/internal/stmtcache"
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +16,8 @@
|
||||||
// the *Conn can be used again. Rows are closed by explicitly calling Close(),
|
// the *Conn can be used again. Rows are closed by explicitly calling Close(),
|
||||||
// calling Next() until it returns false, or when a fatal error occurs.
|
// calling Next() until it returns false, or when a fatal error occurs.
|
||||||
//
|
//
|
||||||
// Once a Rows is closed the only methods that may be called are Close(), Err(), and CommandTag().
|
// Once a Rows is closed the only methods that may be called are Close(), Err(),
|
||||||
|
// and CommandTag().
|
||||||
//
|
//
|
||||||
// Rows is an interface instead of a struct to allow tests to mock Query. However,
|
// Rows is an interface instead of a struct to allow tests to mock Query. However,
|
||||||
// adding a method to an interface is technically a breaking change. Because of this
|
// adding a method to an interface is technically a breaking change. Because of this
|
||||||
|
@ -41,8 +41,15 @@ type Rows interface {
|
||||||
FieldDescriptions() []pgconn.FieldDescription
|
FieldDescriptions() []pgconn.FieldDescription
|
||||||
|
|
||||||
// Next prepares the next row for reading. It returns true if there is another
|
// Next prepares the next row for reading. It returns true if there is another
|
||||||
// row and false if no more rows are available. It automatically closes rows
|
// row and false if no more rows are available or a fatal error has occurred.
|
||||||
// when all rows are read.
|
// It automatically closes rows when all rows are read.
|
||||||
|
//
|
||||||
|
// Callers should check rows.Err() after rows.Next() returns false to detect
|
||||||
|
// whether result-set reading ended prematurely due to an error. See
|
||||||
|
// Conn.Query for details.
|
||||||
|
//
|
||||||
|
// For simpler error handling, consider using the higher-level pgx v5
|
||||||
|
// CollectRows() and ForEachRow() helpers instead.
|
||||||
Next() bool
|
Next() bool
|
||||||
|
|
||||||
// Scan reads the values from the current row into dest values positionally.
|
// Scan reads the values from the current row into dest values positionally.
|
||||||
|
@ -166,14 +173,12 @@ func (rows *baseRows) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows.err != nil && rows.conn != nil && rows.sql != "" {
|
if rows.err != nil && rows.conn != nil && rows.sql != "" {
|
||||||
if stmtcache.IsStatementInvalid(rows.err) {
|
if sc := rows.conn.statementCache; sc != nil {
|
||||||
if sc := rows.conn.statementCache; sc != nil {
|
sc.Invalidate(rows.sql)
|
||||||
sc.Invalidate(rows.sql)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if sc := rows.conn.descriptionCache; sc != nil {
|
if sc := rows.conn.descriptionCache; sc != nil {
|
||||||
sc.Invalidate(rows.sql)
|
sc.Invalidate(rows.sql)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,6 +462,39 @@ func CollectOneRow[T any](rows Rows, fn RowToFunc[T]) (T, error) {
|
||||||
return value, rows.Err()
|
return value, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectExactlyOneRow calls fn for the first row in rows and returns the result.
|
||||||
|
// - If no rows are found returns an error where errors.Is(ErrNoRows) is true.
|
||||||
|
// - If more than 1 row is found returns an error where errors.Is(ErrTooManyRows) is true.
|
||||||
|
func CollectExactlyOneRow[T any](rows Rows, fn RowToFunc[T]) (T, error) {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
value T
|
||||||
|
)
|
||||||
|
|
||||||
|
if !rows.Next() {
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err = fn(rows)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.Next() {
|
||||||
|
var zero T
|
||||||
|
|
||||||
|
return zero, ErrTooManyRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// RowTo returns a T scanned from row.
|
// RowTo returns a T scanned from row.
|
||||||
func RowTo[T any](row CollectableRow) (T, error) {
|
func RowTo[T any](row CollectableRow) (T, error) {
|
||||||
var value T
|
var value T
|
||||||
|
@ -496,7 +534,7 @@ func (rs *mapRowScanner) ScanRow(rows Rows) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number a public fields as row
|
// RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number a public fields as row
|
||||||
// has fields. The row and T fields will by matched by position. If the "db" struct tag is "-" then the field will be
|
// has fields. The row and T fields will be matched by position. If the "db" struct tag is "-" then the field will be
|
||||||
// ignored.
|
// ignored.
|
||||||
func RowToStructByPos[T any](row CollectableRow) (T, error) {
|
func RowToStructByPos[T any](row CollectableRow) (T, error) {
|
||||||
var value T
|
var value T
|
||||||
|
@ -505,7 +543,7 @@ func RowToStructByPos[T any](row CollectableRow) (T, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToAddrOfStructByPos returns the address of a T scanned from row. T must be a struct. T must have the same number a
|
// RowToAddrOfStructByPos returns the address of a T scanned from row. T must be a struct. T must have the same number a
|
||||||
// public fields as row has fields. The row and T fields will by matched by position. If the "db" struct tag is "-" then
|
// public fields as row has fields. The row and T fields will be matched by position. If the "db" struct tag is "-" then
|
||||||
// the field will be ignored.
|
// the field will be ignored.
|
||||||
func RowToAddrOfStructByPos[T any](row CollectableRow) (*T, error) {
|
func RowToAddrOfStructByPos[T any](row CollectableRow) (*T, error) {
|
||||||
var value T
|
var value T
|
||||||
|
@ -560,7 +598,7 @@ func (rs *positionalStructRowScanner) appendScanTargets(dstElemValue reflect.Val
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToStructByName returns a T scanned from row. T must be a struct. T must have the same number of named public
|
// RowToStructByName returns a T scanned from row. T must be a struct. T must have the same number of named public
|
||||||
// fields as row has fields. The row and T fields will by matched by name. The match is case-insensitive. The database
|
// fields as row has fields. The row and T fields will be matched by name. The match is case-insensitive. The database
|
||||||
// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
|
// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
|
||||||
func RowToStructByName[T any](row CollectableRow) (T, error) {
|
func RowToStructByName[T any](row CollectableRow) (T, error) {
|
||||||
var value T
|
var value T
|
||||||
|
@ -569,7 +607,7 @@ func RowToStructByName[T any](row CollectableRow) (T, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToAddrOfStructByName returns the address of a T scanned from row. T must be a struct. T must have the same number
|
// RowToAddrOfStructByName returns the address of a T scanned from row. T must be a struct. T must have the same number
|
||||||
// of named public fields as row has fields. The row and T fields will by matched by name. The match is
|
// of named public fields as row has fields. The row and T fields will be matched by name. The match is
|
||||||
// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
|
// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
|
||||||
// then the field will be ignored.
|
// then the field will be ignored.
|
||||||
func RowToAddrOfStructByName[T any](row CollectableRow) (*T, error) {
|
func RowToAddrOfStructByName[T any](row CollectableRow) (*T, error) {
|
||||||
|
@ -579,7 +617,7 @@ func RowToAddrOfStructByName[T any](row CollectableRow) (*T, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToStructByNameLax returns a T scanned from row. T must be a struct. T must have greater than or equal number of named public
|
// RowToStructByNameLax returns a T scanned from row. T must be a struct. T must have greater than or equal number of named public
|
||||||
// fields as row has fields. The row and T fields will by matched by name. The match is case-insensitive. The database
|
// fields as row has fields. The row and T fields will be matched by name. The match is case-insensitive. The database
|
||||||
// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
|
// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
|
||||||
func RowToStructByNameLax[T any](row CollectableRow) (T, error) {
|
func RowToStructByNameLax[T any](row CollectableRow) (T, error) {
|
||||||
var value T
|
var value T
|
||||||
|
@ -588,7 +626,7 @@ func RowToStructByNameLax[T any](row CollectableRow) (T, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowToAddrOfStructByNameLax returns the address of a T scanned from row. T must be a struct. T must have greater than or
|
// RowToAddrOfStructByNameLax returns the address of a T scanned from row. T must be a struct. T must have greater than or
|
||||||
// equal number of named public fields as row has fields. The row and T fields will by matched by name. The match is
|
// equal number of named public fields as row has fields. The row and T fields will be matched by name. The match is
|
||||||
// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
|
// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
|
||||||
// then the field will be ignored.
|
// then the field will be ignored.
|
||||||
func RowToAddrOfStructByNameLax[T any](row CollectableRow) (*T, error) {
|
func RowToAddrOfStructByNameLax[T any](row CollectableRow) (*T, error) {
|
||||||
|
@ -650,7 +688,7 @@ func (rs *namedStructRowScanner) appendScanTargets(dstElemValue reflect.Value, s
|
||||||
// Field is unexported, skip it.
|
// Field is unexported, skip it.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Handle anoymous struct embedding, but do not try to handle embedded pointers.
|
// Handle anonymous struct embedding, but do not try to handle embedded pointers.
|
||||||
if sf.Anonymous && sf.Type.Kind() == reflect.Struct {
|
if sf.Anonymous && sf.Type.Kind() == reflect.Struct {
|
||||||
scanTargets, err = rs.appendScanTargets(dstElemValue.Field(i), scanTargets, fldDescs)
|
scanTargets, err = rs.appendScanTargets(dstElemValue.Field(i), scanTargets, fldDescs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -659,7 +697,7 @@ func (rs *namedStructRowScanner) appendScanTargets(dstElemValue reflect.Value, s
|
||||||
} else {
|
} else {
|
||||||
dbTag, dbTagPresent := sf.Tag.Lookup(structTagKey)
|
dbTag, dbTagPresent := sf.Tag.Lookup(structTagKey)
|
||||||
if dbTagPresent {
|
if dbTagPresent {
|
||||||
dbTag = strings.Split(dbTag, ",")[0]
|
dbTag, _, _ = strings.Cut(dbTag, ",")
|
||||||
}
|
}
|
||||||
if dbTag == "-" {
|
if dbTag == "-" {
|
||||||
// Field is ignored, skip it.
|
// Field is ignored, skip it.
|
||||||
|
|
141
vendor/github.com/jackc/pgx/v5/stdlib/sql.go
generated
vendored
141
vendor/github.com/jackc/pgx/v5/stdlib/sql.go
generated
vendored
|
@ -14,6 +14,18 @@
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
// Or from a *pgxpool.Pool.
|
||||||
|
//
|
||||||
|
// pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// db, err := stdlib.OpenDBFromPool(pool)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
// Or a pgx.ConnConfig can be used to set configuration not accessible via connection string. In this case the
|
// Or a pgx.ConnConfig can be used to set configuration not accessible via connection string. In this case the
|
||||||
// pgx.ConnConfig must first be registered with the driver. This registration returns a connection string which is used
|
// pgx.ConnConfig must first be registered with the driver. This registration returns a connection string which is used
|
||||||
// with sql.Open.
|
// with sql.Open.
|
||||||
|
@ -74,6 +86,7 @@
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Only intrinsic types should be binary format with database/sql.
|
// Only intrinsic types should be binary format with database/sql.
|
||||||
|
@ -125,14 +138,14 @@ func contains(list []string, y string) bool {
|
||||||
type OptionOpenDB func(*connector)
|
type OptionOpenDB func(*connector)
|
||||||
|
|
||||||
// OptionBeforeConnect provides a callback for before connect. It is passed a shallow copy of the ConnConfig that will
|
// OptionBeforeConnect provides a callback for before connect. It is passed a shallow copy of the ConnConfig that will
|
||||||
// be used to connect, so only its immediate members should be modified.
|
// be used to connect, so only its immediate members should be modified. Used only if db is opened with *pgx.ConnConfig.
|
||||||
func OptionBeforeConnect(bc func(context.Context, *pgx.ConnConfig) error) OptionOpenDB {
|
func OptionBeforeConnect(bc func(context.Context, *pgx.ConnConfig) error) OptionOpenDB {
|
||||||
return func(dc *connector) {
|
return func(dc *connector) {
|
||||||
dc.BeforeConnect = bc
|
dc.BeforeConnect = bc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionAfterConnect provides a callback for after connect.
|
// OptionAfterConnect provides a callback for after connect. Used only if db is opened with *pgx.ConnConfig.
|
||||||
func OptionAfterConnect(ac func(context.Context, *pgx.Conn) error) OptionOpenDB {
|
func OptionAfterConnect(ac func(context.Context, *pgx.Conn) error) OptionOpenDB {
|
||||||
return func(dc *connector) {
|
return func(dc *connector) {
|
||||||
dc.AfterConnect = ac
|
dc.AfterConnect = ac
|
||||||
|
@ -191,13 +204,42 @@ func GetConnector(config pgx.ConnConfig, opts ...OptionOpenDB) driver.Connector
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPoolConnector creates a new driver.Connector from the given *pgxpool.Pool. By using this be sure to set the
|
||||||
|
// maximum idle connections of the *sql.DB created with this connector to zero since they must be managed from the
|
||||||
|
// *pgxpool.Pool. This is required to avoid acquiring all the connections from the pgxpool and starving any direct
|
||||||
|
// users of the pgxpool.
|
||||||
|
func GetPoolConnector(pool *pgxpool.Pool, opts ...OptionOpenDB) driver.Connector {
|
||||||
|
c := connector{
|
||||||
|
pool: pool,
|
||||||
|
ResetSession: func(context.Context, *pgx.Conn) error { return nil }, // noop reset session by default
|
||||||
|
driver: pgxDriver,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB {
|
func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB {
|
||||||
c := GetConnector(config, opts...)
|
c := GetConnector(config, opts...)
|
||||||
return sql.OpenDB(c)
|
return sql.OpenDB(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenDBFromPool creates a new *sql.DB from the given *pgxpool.Pool. Note that this method automatically sets the
|
||||||
|
// maximum number of idle connections in *sql.DB to zero, since they must be managed from the *pgxpool.Pool. This is
|
||||||
|
// required to avoid acquiring all the connections from the pgxpool and starving any direct users of the pgxpool.
|
||||||
|
func OpenDBFromPool(pool *pgxpool.Pool, opts ...OptionOpenDB) *sql.DB {
|
||||||
|
c := GetPoolConnector(pool, opts...)
|
||||||
|
db := sql.OpenDB(c)
|
||||||
|
db.SetMaxIdleConns(0)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
type connector struct {
|
type connector struct {
|
||||||
pgx.ConnConfig
|
pgx.ConnConfig
|
||||||
|
pool *pgxpool.Pool
|
||||||
BeforeConnect func(context.Context, *pgx.ConnConfig) error // function to call before creation of every new connection
|
BeforeConnect func(context.Context, *pgx.ConnConfig) error // function to call before creation of every new connection
|
||||||
AfterConnect func(context.Context, *pgx.Conn) error // function to call after creation of every new connection
|
AfterConnect func(context.Context, *pgx.Conn) error // function to call after creation of every new connection
|
||||||
ResetSession func(context.Context, *pgx.Conn) error // function is called before a connection is reused
|
ResetSession func(context.Context, *pgx.Conn) error // function is called before a connection is reused
|
||||||
|
@ -207,25 +249,53 @@ type connector struct {
|
||||||
// Connect implement driver.Connector interface
|
// Connect implement driver.Connector interface
|
||||||
func (c connector) Connect(ctx context.Context) (driver.Conn, error) {
|
func (c connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
connConfig pgx.ConnConfig
|
||||||
conn *pgx.Conn
|
conn *pgx.Conn
|
||||||
|
close func(context.Context) error
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create a shallow copy of the config, so that BeforeConnect can safely modify it
|
if c.pool == nil {
|
||||||
connConfig := c.ConnConfig
|
// Create a shallow copy of the config, so that BeforeConnect can safely modify it
|
||||||
if err = c.BeforeConnect(ctx, &connConfig); err != nil {
|
connConfig = c.ConnConfig
|
||||||
return nil, err
|
|
||||||
|
if err = c.BeforeConnect(ctx, &connConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn, err = pgx.ConnectConfig(ctx, &connConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.AfterConnect(ctx, conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
close = conn.Close
|
||||||
|
} else {
|
||||||
|
var pconn *pgxpool.Conn
|
||||||
|
|
||||||
|
pconn, err = c.pool.Acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = pconn.Conn()
|
||||||
|
|
||||||
|
close = func(_ context.Context) error {
|
||||||
|
pconn.Release()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn, err = pgx.ConnectConfig(ctx, &connConfig); err != nil {
|
return &Conn{
|
||||||
return nil, err
|
conn: conn,
|
||||||
}
|
close: close,
|
||||||
|
driver: c.driver,
|
||||||
if err = c.AfterConnect(ctx, conn); err != nil {
|
connConfig: connConfig,
|
||||||
return nil, err
|
resetSessionFunc: c.ResetSession,
|
||||||
}
|
psRefCounts: make(map[*pgconn.StatementDescription]int),
|
||||||
|
}, nil
|
||||||
return &Conn{conn: conn, driver: c.driver, connConfig: connConfig, resetSessionFunc: c.ResetSession}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver implement driver.Connector interface
|
// Driver implement driver.Connector interface
|
||||||
|
@ -302,9 +372,11 @@ func (dc *driverConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
|
close: conn.Close,
|
||||||
driver: dc.driver,
|
driver: dc.driver,
|
||||||
connConfig: *connConfig,
|
connConfig: *connConfig,
|
||||||
resetSessionFunc: func(context.Context, *pgx.Conn) error { return nil },
|
resetSessionFunc: func(context.Context, *pgx.Conn) error { return nil },
|
||||||
|
psRefCounts: make(map[*pgconn.StatementDescription]int),
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
@ -326,11 +398,19 @@ func UnregisterConnConfig(connStr string) {
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn *pgx.Conn
|
conn *pgx.Conn
|
||||||
psCount int64 // Counter used for creating unique prepared statement names
|
close func(context.Context) error
|
||||||
driver *Driver
|
driver *Driver
|
||||||
connConfig pgx.ConnConfig
|
connConfig pgx.ConnConfig
|
||||||
resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused
|
resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused
|
||||||
lastResetSessionTime time.Time
|
lastResetSessionTime time.Time
|
||||||
|
|
||||||
|
// psRefCounts contains reference counts for prepared statements. Prepare uses the underlying pgx logic to generate
|
||||||
|
// deterministic statement names from the statement text. If this query has already been prepared then the existing
|
||||||
|
// *pgconn.StatementDescription will be returned. However, this means that if Close is called on the returned Stmt
|
||||||
|
// then the underlying prepared statement will be closed even when the underlying prepared statement is still in use
|
||||||
|
// by another database/sql Stmt. To prevent this psRefCounts keeps track of how many database/sql statements are using
|
||||||
|
// the same underlying statement and only closes the underlying statement when the reference count reaches 0.
|
||||||
|
psRefCounts map[*pgconn.StatementDescription]int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn returns the underlying *pgx.Conn
|
// Conn returns the underlying *pgx.Conn
|
||||||
|
@ -347,13 +427,11 @@ func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
|
|
||||||
name := fmt.Sprintf("pgx_%d", c.psCount)
|
sd, err := c.conn.Prepare(ctx, query, query)
|
||||||
c.psCount++
|
|
||||||
|
|
||||||
sd, err := c.conn.Prepare(ctx, name, query)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
c.psRefCounts[sd]++
|
||||||
|
|
||||||
return &Stmt{sd: sd, conn: c}, nil
|
return &Stmt{sd: sd, conn: c}, nil
|
||||||
}
|
}
|
||||||
|
@ -361,7 +439,7 @@ func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return c.conn.Close(ctx)
|
return c.close(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Begin() (driver.Tx, error) {
|
func (c *Conn) Begin() (driver.Tx, error) {
|
||||||
|
@ -470,7 +548,7 @@ func (c *Conn) ResetSession(ctx context.Context) error {
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(c.lastResetSessionTime) > time.Second {
|
if now.Sub(c.lastResetSessionTime) > time.Second {
|
||||||
if err := c.conn.PgConn().CheckConn(); err != nil {
|
if err := c.conn.PgConn().Ping(ctx); err != nil {
|
||||||
return driver.ErrBadConn
|
return driver.ErrBadConn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +565,16 @@ type Stmt struct {
|
||||||
func (s *Stmt) Close() error {
|
func (s *Stmt) Close() error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return s.conn.conn.Deallocate(ctx, s.sd.Name)
|
|
||||||
|
refCount := s.conn.psRefCounts[s.sd]
|
||||||
|
if refCount == 1 {
|
||||||
|
delete(s.conn.psRefCounts, s.sd)
|
||||||
|
} else {
|
||||||
|
s.conn.psRefCounts[s.sd]--
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.conn.conn.Deallocate(ctx, s.sd.SQL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) NumInput() int {
|
func (s *Stmt) NumInput() int {
|
||||||
|
@ -499,7 +586,7 @@ func (s *Stmt) Exec(argsV []driver.Value) (driver.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) ExecContext(ctx context.Context, argsV []driver.NamedValue) (driver.Result, error) {
|
func (s *Stmt) ExecContext(ctx context.Context, argsV []driver.NamedValue) (driver.Result, error) {
|
||||||
return s.conn.ExecContext(ctx, s.sd.Name, argsV)
|
return s.conn.ExecContext(ctx, s.sd.SQL, argsV)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) Query(argsV []driver.Value) (driver.Rows, error) {
|
func (s *Stmt) Query(argsV []driver.Value) (driver.Rows, error) {
|
||||||
|
@ -507,7 +594,7 @@ func (s *Stmt) Query(argsV []driver.Value) (driver.Rows, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) QueryContext(ctx context.Context, argsV []driver.NamedValue) (driver.Rows, error) {
|
func (s *Stmt) QueryContext(ctx context.Context, argsV []driver.NamedValue) (driver.Rows, error) {
|
||||||
return s.conn.QueryContext(ctx, s.sd.Name, argsV)
|
return s.conn.QueryContext(ctx, s.sd.SQL, argsV)
|
||||||
}
|
}
|
||||||
|
|
||||||
type rowValueFunc func(src []byte) (driver.Value, error)
|
type rowValueFunc func(src []byte) (driver.Value, error)
|
||||||
|
|
74
vendor/github.com/jackc/puddle/v2/CHANGELOG.md
generated
vendored
Normal file
74
vendor/github.com/jackc/puddle/v2/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# 2.2.1 (July 15, 2023)
|
||||||
|
|
||||||
|
* Fix: CreateResource cannot overflow pool. This changes documented behavior of CreateResource. Previously,
|
||||||
|
CreateResource could create a resource even if the pool was full. This could cause the pool to overflow. While this
|
||||||
|
was documented, it was documenting incorrect behavior. CreateResource now returns an error if the pool is full.
|
||||||
|
|
||||||
|
# 2.2.0 (February 11, 2023)
|
||||||
|
|
||||||
|
* Use Go 1.19 atomics and drop go.uber.org/atomic dependency
|
||||||
|
|
||||||
|
# 2.1.2 (November 12, 2022)
|
||||||
|
|
||||||
|
* Restore support to Go 1.18 via go.uber.org/atomic
|
||||||
|
|
||||||
|
# 2.1.1 (November 11, 2022)
|
||||||
|
|
||||||
|
* Fix create resource concurrently with Stat call race
|
||||||
|
|
||||||
|
# 2.1.0 (October 28, 2022)
|
||||||
|
|
||||||
|
* Concurrency control is now implemented with a semaphore. This simplifies some internal logic, resolves a few error conditions (including a deadlock), and improves performance. (Jan Dubsky)
|
||||||
|
* Go 1.19 is now required for the improved atomic support.
|
||||||
|
|
||||||
|
# 2.0.1 (October 28, 2022)
|
||||||
|
|
||||||
|
* Fix race condition when Close is called concurrently with multiple constructors
|
||||||
|
|
||||||
|
# 2.0.0 (September 17, 2022)
|
||||||
|
|
||||||
|
* Use generics instead of interface{} (Столяров Владимир Алексеевич)
|
||||||
|
* Add Reset
|
||||||
|
* Do not cancel resource construction when Acquire is canceled
|
||||||
|
* NewPool takes Config
|
||||||
|
|
||||||
|
# 1.3.0 (August 27, 2022)
|
||||||
|
|
||||||
|
* Acquire creates resources in background to allow creation to continue after Acquire is canceled (James Hartig)
|
||||||
|
|
||||||
|
# 1.2.1 (December 2, 2021)
|
||||||
|
|
||||||
|
* TryAcquire now does not block when background constructing resource
|
||||||
|
|
||||||
|
# 1.2.0 (November 20, 2021)
|
||||||
|
|
||||||
|
* Add TryAcquire (A. Jensen)
|
||||||
|
* Fix: remove memory leak / unintentionally pinned memory when shrinking slices (Alexander Staubo)
|
||||||
|
* Fix: Do not leave pool locked after panic from nil context
|
||||||
|
|
||||||
|
# 1.1.4 (September 11, 2021)
|
||||||
|
|
||||||
|
* Fix: Deadlock in CreateResource if pool was closed during resource acquisition (Dmitriy Matrenichev)
|
||||||
|
|
||||||
|
# 1.1.3 (December 3, 2020)
|
||||||
|
|
||||||
|
* Fix: Failed resource creation could cause concurrent Acquire to hang. (Evgeny Vanslov)
|
||||||
|
|
||||||
|
# 1.1.2 (September 26, 2020)
|
||||||
|
|
||||||
|
* Fix: Resource.Destroy no longer removes itself from the pool before its destructor has completed.
|
||||||
|
* Fix: Prevent crash when pool is closed while resource is being created.
|
||||||
|
|
||||||
|
# 1.1.1 (April 2, 2020)
|
||||||
|
|
||||||
|
* Pool.Close can be safely called multiple times
|
||||||
|
* AcquireAllIDle immediately returns nil if pool is closed
|
||||||
|
* CreateResource checks if pool is closed before taking any action
|
||||||
|
* Fix potential race condition when CreateResource and Close are called concurrently. CreateResource now checks if pool is closed before adding newly created resource to pool.
|
||||||
|
|
||||||
|
# 1.1.0 (February 5, 2020)
|
||||||
|
|
||||||
|
* Use runtime.nanotime for faster tracking of acquire time and last usage time.
|
||||||
|
* Track resource idle time to enable client health check logic. (Patrick Ellul)
|
||||||
|
* Add CreateResource to construct a new resource without acquiring it. (Patrick Ellul)
|
||||||
|
* Fix deadlock race when acquire is cancelled. (Michael Tharp)
|
22
vendor/github.com/jackc/puddle/v2/LICENSE
generated
vendored
Normal file
22
vendor/github.com/jackc/puddle/v2/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (c) 2018 Jack Christensen
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
80
vendor/github.com/jackc/puddle/v2/README.md
generated
vendored
Normal file
80
vendor/github.com/jackc/puddle/v2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
[![](https://godoc.org/github.com/jackc/puddle?status.svg)](https://godoc.org/github.com/jackc/puddle)
|
||||||
|
![Build Status](https://github.com/jackc/puddle/actions/workflows/ci.yml/badge.svg)
|
||||||
|
|
||||||
|
# Puddle
|
||||||
|
|
||||||
|
Puddle is a tiny generic resource pool library for Go that uses the standard
|
||||||
|
context library to signal cancellation of acquires. It is designed to contain
|
||||||
|
the minimum functionality required for a resource pool. It can be used directly
|
||||||
|
or it can be used as the base for a domain specific resource pool. For example,
|
||||||
|
a database connection pool may use puddle internally and implement health checks
|
||||||
|
and keep-alive behavior without needing to implement any concurrent code of its
|
||||||
|
own.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Acquire cancellation via context standard library
|
||||||
|
* Statistics API for monitoring pool pressure
|
||||||
|
* No dependencies outside of standard library and golang.org/x/sync
|
||||||
|
* High performance
|
||||||
|
* 100% test coverage of reachable code
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/jackc/puddle/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
constructor := func(context.Context) (net.Conn, error) {
|
||||||
|
return net.Dial("tcp", "127.0.0.1:8080")
|
||||||
|
}
|
||||||
|
destructor := func(value net.Conn) {
|
||||||
|
value.Close()
|
||||||
|
}
|
||||||
|
maxPoolSize := int32(10)
|
||||||
|
|
||||||
|
pool, err := puddle.NewPool(&puddle.Config[net.Conn]{Constructor: constructor, Destructor: destructor, MaxSize: maxPoolSize})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire resource from the pool.
|
||||||
|
res, err := pool.Acquire(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use resource.
|
||||||
|
_, err = res.Value().Write([]byte{1})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release when done.
|
||||||
|
res.Release()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Puddle is stable and feature complete.
|
||||||
|
|
||||||
|
* Bug reports and fixes are welcome.
|
||||||
|
* New features will usually not be accepted if they can be feasibly implemented in a wrapper.
|
||||||
|
* Performance optimizations will usually not be accepted unless the performance issue rises to the level of a bug.
|
||||||
|
|
||||||
|
## Supported Go Versions
|
||||||
|
|
||||||
|
puddle supports the same versions of Go that are supported by the Go project. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases. This means puddle supports Go 1.19 and higher.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
24
vendor/github.com/jackc/puddle/v2/context.go
generated
vendored
Normal file
24
vendor/github.com/jackc/puddle/v2/context.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// valueCancelCtx combines two contexts into one. One context is used for values and the other is used for cancellation.
|
||||||
|
type valueCancelCtx struct {
|
||||||
|
valueCtx context.Context
|
||||||
|
cancelCtx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *valueCancelCtx) Deadline() (time.Time, bool) { return ctx.cancelCtx.Deadline() }
|
||||||
|
func (ctx *valueCancelCtx) Done() <-chan struct{} { return ctx.cancelCtx.Done() }
|
||||||
|
func (ctx *valueCancelCtx) Err() error { return ctx.cancelCtx.Err() }
|
||||||
|
func (ctx *valueCancelCtx) Value(key any) any { return ctx.valueCtx.Value(key) }
|
||||||
|
|
||||||
|
func newValueCancelCtx(valueCtx, cancelContext context.Context) context.Context {
|
||||||
|
return &valueCancelCtx{
|
||||||
|
valueCtx: valueCtx,
|
||||||
|
cancelCtx: cancelContext,
|
||||||
|
}
|
||||||
|
}
|
11
vendor/github.com/jackc/puddle/v2/doc.go
generated
vendored
Normal file
11
vendor/github.com/jackc/puddle/v2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Package puddle is a generic resource pool with type-parametrized api.
|
||||||
|
/*
|
||||||
|
|
||||||
|
Puddle is a tiny generic resource pool library for Go that uses the standard
|
||||||
|
context library to signal cancellation of acquires. It is designed to contain
|
||||||
|
the minimum functionality a resource pool needs that cannot be implemented
|
||||||
|
without concurrency concerns. For example, a database connection pool may use
|
||||||
|
puddle internally and implement health checks and keep-alive behavior without
|
||||||
|
needing to implement any concurrent code of its own.
|
||||||
|
*/
|
||||||
|
package puddle
|
85
vendor/github.com/jackc/puddle/v2/internal/genstack/gen_stack.go
generated
vendored
Normal file
85
vendor/github.com/jackc/puddle/v2/internal/genstack/gen_stack.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package genstack
|
||||||
|
|
||||||
|
// GenStack implements a generational stack.
|
||||||
|
//
|
||||||
|
// GenStack works as common stack except for the fact that all elements in the
|
||||||
|
// older generation are guaranteed to be popped before any element in the newer
|
||||||
|
// generation. New elements are always pushed to the current (newest)
|
||||||
|
// generation.
|
||||||
|
//
|
||||||
|
// We could also say that GenStack behaves as a stack in case of a single
|
||||||
|
// generation, but it behaves as a queue of individual generation stacks.
|
||||||
|
type GenStack[T any] struct {
|
||||||
|
// We can represent arbitrary number of generations using 2 stacks. The
|
||||||
|
// new stack stores all new pushes and the old stack serves all reads.
|
||||||
|
// Old stack can represent multiple generations. If old == new, then all
|
||||||
|
// elements pushed in previous (not current) generations have already
|
||||||
|
// been popped.
|
||||||
|
|
||||||
|
old *stack[T]
|
||||||
|
new *stack[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenStack creates a new empty GenStack.
|
||||||
|
func NewGenStack[T any]() *GenStack[T] {
|
||||||
|
s := &stack[T]{}
|
||||||
|
return &GenStack[T]{
|
||||||
|
old: s,
|
||||||
|
new: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GenStack[T]) Pop() (T, bool) {
|
||||||
|
// Pushes always append to the new stack, so if the old once becomes
|
||||||
|
// empty, it will remail empty forever.
|
||||||
|
if s.old.len() == 0 && s.old != s.new {
|
||||||
|
s.old = s.new
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.old.len() == 0 {
|
||||||
|
var zero T
|
||||||
|
return zero, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.old.pop(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push pushes a new element at the top of the stack.
|
||||||
|
func (s *GenStack[T]) Push(v T) { s.new.push(v) }
|
||||||
|
|
||||||
|
// NextGen starts a new stack generation.
|
||||||
|
func (s *GenStack[T]) NextGen() {
|
||||||
|
if s.old == s.new {
|
||||||
|
s.new = &stack[T]{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to pop from the old stack to the top of the new stack. Let's
|
||||||
|
// have an example:
|
||||||
|
//
|
||||||
|
// Old: <bottom> 4 3 2 1
|
||||||
|
// New: <bottom> 8 7 6 5
|
||||||
|
// PopOrder: 1 2 3 4 5 6 7 8
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// To preserve pop order, we have to take all elements from the old
|
||||||
|
// stack and push them to the top of new stack:
|
||||||
|
//
|
||||||
|
// New: 8 7 6 5 4 3 2 1
|
||||||
|
//
|
||||||
|
s.new.push(s.old.takeAll()...)
|
||||||
|
|
||||||
|
// We have the old stack allocated and empty, so why not to reuse it as
|
||||||
|
// new new stack.
|
||||||
|
s.old, s.new = s.new, s.old
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns number of elements in the stack.
|
||||||
|
func (s *GenStack[T]) Len() int {
|
||||||
|
l := s.old.len()
|
||||||
|
if s.old != s.new {
|
||||||
|
l += s.new.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
39
vendor/github.com/jackc/puddle/v2/internal/genstack/stack.go
generated
vendored
Normal file
39
vendor/github.com/jackc/puddle/v2/internal/genstack/stack.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package genstack
|
||||||
|
|
||||||
|
// stack is a wrapper around an array implementing a stack.
|
||||||
|
//
|
||||||
|
// We cannot use slice to represent the stack because append might change the
|
||||||
|
// pointer value of the slice. That would be an issue in GenStack
|
||||||
|
// implementation.
|
||||||
|
type stack[T any] struct {
|
||||||
|
arr []T
|
||||||
|
}
|
||||||
|
|
||||||
|
// push pushes a new element at the top of a stack.
|
||||||
|
func (s *stack[T]) push(vs ...T) { s.arr = append(s.arr, vs...) }
|
||||||
|
|
||||||
|
// pop pops the stack top-most element.
|
||||||
|
//
|
||||||
|
// If stack length is zero, this method panics.
|
||||||
|
func (s *stack[T]) pop() T {
|
||||||
|
idx := s.len() - 1
|
||||||
|
val := s.arr[idx]
|
||||||
|
|
||||||
|
// Avoid memory leak
|
||||||
|
var zero T
|
||||||
|
s.arr[idx] = zero
|
||||||
|
|
||||||
|
s.arr = s.arr[:idx]
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeAll returns all elements in the stack in order as they are stored - i.e.
|
||||||
|
// the top-most stack element is the last one.
|
||||||
|
func (s *stack[T]) takeAll() []T {
|
||||||
|
arr := s.arr
|
||||||
|
s.arr = nil
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// len returns number of elements in the stack.
|
||||||
|
func (s *stack[T]) len() int { return len(s.arr) }
|
32
vendor/github.com/jackc/puddle/v2/log.go
generated
vendored
Normal file
32
vendor/github.com/jackc/puddle/v2/log.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
type ints interface {
|
||||||
|
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// log2Int returns log2 of an integer. This function panics if val < 0. For val
|
||||||
|
// == 0, returns 0.
|
||||||
|
func log2Int[T ints](val T) uint8 {
|
||||||
|
if val <= 0 {
|
||||||
|
panic("log2 of non-positive number does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return log2IntRange(val, 0, uint8(8*unsafe.Sizeof(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func log2IntRange[T ints](val T, begin, end uint8) uint8 {
|
||||||
|
length := end - begin
|
||||||
|
if length == 1 {
|
||||||
|
return begin
|
||||||
|
}
|
||||||
|
|
||||||
|
delim := begin + length/2
|
||||||
|
mask := T(1) << delim
|
||||||
|
if mask > val {
|
||||||
|
return log2IntRange(val, begin, delim)
|
||||||
|
} else {
|
||||||
|
return log2IntRange(val, delim, end)
|
||||||
|
}
|
||||||
|
}
|
13
vendor/github.com/jackc/puddle/v2/nanotime_time.go
generated
vendored
Normal file
13
vendor/github.com/jackc/puddle/v2/nanotime_time.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//go:build purego || appengine || js
|
||||||
|
|
||||||
|
// This file contains the safe implementation of nanotime using time.Now().
|
||||||
|
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nanotime() int64 {
|
||||||
|
return time.Now().UnixNano()
|
||||||
|
}
|
12
vendor/github.com/jackc/puddle/v2/nanotime_unsafe.go
generated
vendored
Normal file
12
vendor/github.com/jackc/puddle/v2/nanotime_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build !purego && !appengine && !js
|
||||||
|
|
||||||
|
// This file contains the implementation of nanotime using runtime.nanotime.
|
||||||
|
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var _ = unsafe.Sizeof(0)
|
||||||
|
|
||||||
|
//go:linkname nanotime runtime.nanotime
|
||||||
|
func nanotime() int64
|
696
vendor/github.com/jackc/puddle/v2/pool.go
generated
vendored
Normal file
696
vendor/github.com/jackc/puddle/v2/pool.go
generated
vendored
Normal file
|
@ -0,0 +1,696 @@
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/puddle/v2/internal/genstack"
|
||||||
|
"golang.org/x/sync/semaphore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resourceStatusConstructing = 0
|
||||||
|
resourceStatusIdle = iota
|
||||||
|
resourceStatusAcquired = iota
|
||||||
|
resourceStatusHijacked = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrClosedPool occurs on an attempt to acquire a connection from a closed pool
|
||||||
|
// or a pool that is closed while the acquire is waiting.
|
||||||
|
var ErrClosedPool = errors.New("closed pool")
|
||||||
|
|
||||||
|
// ErrNotAvailable occurs on an attempt to acquire a resource from a pool
|
||||||
|
// that is at maximum capacity and has no available resources.
|
||||||
|
var ErrNotAvailable = errors.New("resource not available")
|
||||||
|
|
||||||
|
// Constructor is a function called by the pool to construct a resource.
|
||||||
|
type Constructor[T any] func(ctx context.Context) (res T, err error)
|
||||||
|
|
||||||
|
// Destructor is a function called by the pool to destroy a resource.
|
||||||
|
type Destructor[T any] func(res T)
|
||||||
|
|
||||||
|
// Resource is the resource handle returned by acquiring from the pool.
|
||||||
|
type Resource[T any] struct {
|
||||||
|
value T
|
||||||
|
pool *Pool[T]
|
||||||
|
creationTime time.Time
|
||||||
|
lastUsedNano int64
|
||||||
|
poolResetCount int
|
||||||
|
status byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the resource value.
|
||||||
|
func (res *Resource[T]) Value() T {
|
||||||
|
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||||
|
panic("tried to access resource that is not acquired or hijacked")
|
||||||
|
}
|
||||||
|
return res.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release returns the resource to the pool. res must not be subsequently used.
|
||||||
|
func (res *Resource[T]) Release() {
|
||||||
|
if res.status != resourceStatusAcquired {
|
||||||
|
panic("tried to release resource that is not acquired")
|
||||||
|
}
|
||||||
|
res.pool.releaseAcquiredResource(res, nanotime())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
|
||||||
|
// will not change. res must not be subsequently used.
|
||||||
|
func (res *Resource[T]) ReleaseUnused() {
|
||||||
|
if res.status != resourceStatusAcquired {
|
||||||
|
panic("tried to release resource that is not acquired")
|
||||||
|
}
|
||||||
|
res.pool.releaseAcquiredResource(res, res.lastUsedNano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy returns the resource to the pool for destruction. res must not be
|
||||||
|
// subsequently used.
|
||||||
|
func (res *Resource[T]) Destroy() {
|
||||||
|
if res.status != resourceStatusAcquired {
|
||||||
|
panic("tried to destroy resource that is not acquired")
|
||||||
|
}
|
||||||
|
go res.pool.destroyAcquiredResource(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack assumes ownership of the resource from the pool. Caller is responsible
|
||||||
|
// for cleanup of resource value.
|
||||||
|
func (res *Resource[T]) Hijack() {
|
||||||
|
if res.status != resourceStatusAcquired {
|
||||||
|
panic("tried to hijack resource that is not acquired")
|
||||||
|
}
|
||||||
|
res.pool.hijackAcquiredResource(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreationTime returns when the resource was created by the pool.
|
||||||
|
func (res *Resource[T]) CreationTime() time.Time {
|
||||||
|
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||||
|
panic("tried to access resource that is not acquired or hijacked")
|
||||||
|
}
|
||||||
|
return res.creationTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary time
|
||||||
|
// (a monotonic time). Returns creation time if Release has never been called. This is only useful to compare with
|
||||||
|
// other calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
|
||||||
|
func (res *Resource[T]) LastUsedNanotime() int64 {
|
||||||
|
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||||
|
panic("tried to access resource that is not acquired or hijacked")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.lastUsedNano
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleDuration returns the duration since Release was last called on the resource. This is equivalent to subtracting
|
||||||
|
// LastUsedNanotime to the current nanotime.
|
||||||
|
func (res *Resource[T]) IdleDuration() time.Duration {
|
||||||
|
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||||
|
panic("tried to access resource that is not acquired or hijacked")
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(nanotime() - res.lastUsedNano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool is a concurrency-safe resource pool.
|
||||||
|
type Pool[T any] struct {
|
||||||
|
// mux is the pool internal lock. Any modification of shared state of
|
||||||
|
// the pool (but Acquires of acquireSem) must be performed only by
|
||||||
|
// holder of the lock. Long running operations are not allowed when mux
|
||||||
|
// is held.
|
||||||
|
mux sync.Mutex
|
||||||
|
// acquireSem provides an allowance to acquire a resource.
|
||||||
|
//
|
||||||
|
// Releases are allowed only when caller holds mux. Acquires have to
|
||||||
|
// happen before mux is locked (doesn't apply to semaphore.TryAcquire in
|
||||||
|
// AcquireAllIdle).
|
||||||
|
acquireSem *semaphore.Weighted
|
||||||
|
destructWG sync.WaitGroup
|
||||||
|
|
||||||
|
allResources resList[T]
|
||||||
|
idleResources *genstack.GenStack[*Resource[T]]
|
||||||
|
|
||||||
|
constructor Constructor[T]
|
||||||
|
destructor Destructor[T]
|
||||||
|
maxSize int32
|
||||||
|
|
||||||
|
acquireCount int64
|
||||||
|
acquireDuration time.Duration
|
||||||
|
emptyAcquireCount int64
|
||||||
|
canceledAcquireCount atomic.Int64
|
||||||
|
|
||||||
|
resetCount int
|
||||||
|
|
||||||
|
baseAcquireCtx context.Context
|
||||||
|
cancelBaseAcquireCtx context.CancelFunc
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config[T any] struct {
|
||||||
|
Constructor Constructor[T]
|
||||||
|
Destructor Destructor[T]
|
||||||
|
MaxSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool creates a new pool. Panics if maxSize is less than 1.
|
||||||
|
func NewPool[T any](config *Config[T]) (*Pool[T], error) {
|
||||||
|
if config.MaxSize < 1 {
|
||||||
|
return nil, errors.New("MaxSize must be >= 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAcquireCtx, cancelBaseAcquireCtx := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
return &Pool[T]{
|
||||||
|
acquireSem: semaphore.NewWeighted(int64(config.MaxSize)),
|
||||||
|
idleResources: genstack.NewGenStack[*Resource[T]](),
|
||||||
|
maxSize: config.MaxSize,
|
||||||
|
constructor: config.Constructor,
|
||||||
|
destructor: config.Destructor,
|
||||||
|
baseAcquireCtx: baseAcquireCtx,
|
||||||
|
cancelBaseAcquireCtx: cancelBaseAcquireCtx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close destroys all resources in the pool and rejects future Acquire calls.
|
||||||
|
// Blocks until all resources are returned to pool and destroyed.
|
||||||
|
func (p *Pool[T]) Close() {
|
||||||
|
defer p.destructWG.Wait()
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.closed = true
|
||||||
|
p.cancelBaseAcquireCtx()
|
||||||
|
|
||||||
|
for res, ok := p.idleResources.Pop(); ok; res, ok = p.idleResources.Pop() {
|
||||||
|
p.allResources.remove(res)
|
||||||
|
go p.destructResourceValue(res.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat is a snapshot of Pool statistics.
|
||||||
|
type Stat struct {
|
||||||
|
constructingResources int32
|
||||||
|
acquiredResources int32
|
||||||
|
idleResources int32
|
||||||
|
maxResources int32
|
||||||
|
acquireCount int64
|
||||||
|
acquireDuration time.Duration
|
||||||
|
emptyAcquireCount int64
|
||||||
|
canceledAcquireCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalResources returns the total number of resources currently in the pool.
|
||||||
|
// The value is the sum of ConstructingResources, AcquiredResources, and
|
||||||
|
// IdleResources.
|
||||||
|
func (s *Stat) TotalResources() int32 {
|
||||||
|
return s.constructingResources + s.acquiredResources + s.idleResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstructingResources returns the number of resources with construction in progress in
|
||||||
|
// the pool.
|
||||||
|
func (s *Stat) ConstructingResources() int32 {
|
||||||
|
return s.constructingResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquiredResources returns the number of currently acquired resources in the pool.
|
||||||
|
func (s *Stat) AcquiredResources() int32 {
|
||||||
|
return s.acquiredResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleResources returns the number of currently idle resources in the pool.
|
||||||
|
func (s *Stat) IdleResources() int32 {
|
||||||
|
return s.idleResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxResources returns the maximum size of the pool.
|
||||||
|
func (s *Stat) MaxResources() int32 {
|
||||||
|
return s.maxResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireCount returns the cumulative count of successful acquires from the pool.
|
||||||
|
func (s *Stat) AcquireCount() int64 {
|
||||||
|
return s.acquireCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireDuration returns the total duration of all successful acquires from
|
||||||
|
// the pool.
|
||||||
|
func (s *Stat) AcquireDuration() time.Duration {
|
||||||
|
return s.acquireDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyAcquireCount returns the cumulative count of successful acquires from the pool
|
||||||
|
// that waited for a resource to be released or constructed because the pool was
|
||||||
|
// empty.
|
||||||
|
func (s *Stat) EmptyAcquireCount() int64 {
|
||||||
|
return s.emptyAcquireCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAcquireCount returns the cumulative count of acquires from the pool
|
||||||
|
// that were canceled by a context.
|
||||||
|
func (s *Stat) CanceledAcquireCount() int64 {
|
||||||
|
return s.canceledAcquireCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns the current pool statistics.
|
||||||
|
func (p *Pool[T]) Stat() *Stat {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
s := &Stat{
|
||||||
|
maxResources: p.maxSize,
|
||||||
|
acquireCount: p.acquireCount,
|
||||||
|
emptyAcquireCount: p.emptyAcquireCount,
|
||||||
|
canceledAcquireCount: p.canceledAcquireCount.Load(),
|
||||||
|
acquireDuration: p.acquireDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, res := range p.allResources {
|
||||||
|
switch res.status {
|
||||||
|
case resourceStatusConstructing:
|
||||||
|
s.constructingResources += 1
|
||||||
|
case resourceStatusIdle:
|
||||||
|
s.idleResources += 1
|
||||||
|
case resourceStatusAcquired:
|
||||||
|
s.acquiredResources += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryAcquireIdleResource checks if there is any idle resource. If there is
|
||||||
|
// some, this method removes it from idle list and returns it. If the idle pool
|
||||||
|
// is empty, this method returns nil and doesn't modify the idleResources slice.
|
||||||
|
//
|
||||||
|
// WARNING: Caller of this method must hold the pool mutex!
|
||||||
|
func (p *Pool[T]) tryAcquireIdleResource() *Resource[T] {
|
||||||
|
res, ok := p.idleResources.Pop()
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status = resourceStatusAcquired
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNewResource creates a new resource and inserts it into list of pool
|
||||||
|
// resources.
|
||||||
|
//
|
||||||
|
// WARNING: Caller of this method must hold the pool mutex!
|
||||||
|
func (p *Pool[T]) createNewResource() *Resource[T] {
|
||||||
|
res := &Resource[T]{
|
||||||
|
pool: p,
|
||||||
|
creationTime: time.Now(),
|
||||||
|
lastUsedNano: nanotime(),
|
||||||
|
poolResetCount: p.resetCount,
|
||||||
|
status: resourceStatusConstructing,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.allResources.append(res)
|
||||||
|
p.destructWG.Add(1)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire gets a resource from the pool. If no resources are available and the pool is not at maximum capacity it will
|
||||||
|
// create a new resource. If the pool is at maximum capacity it will block until a resource is available. ctx can be
|
||||||
|
// used to cancel the Acquire.
|
||||||
|
//
|
||||||
|
// If Acquire creates a new resource the resource constructor function will receive a context that delegates Value() to
|
||||||
|
// ctx. Canceling ctx will cause Acquire to return immediately but it will not cancel the resource creation. This avoids
|
||||||
|
// the problem of it being impossible to create resources when the time to create a resource is greater than any one
|
||||||
|
// caller of Acquire is willing to wait.
|
||||||
|
func (p *Pool[T]) Acquire(ctx context.Context) (_ *Resource[T], err error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
p.canceledAcquireCount.Add(1)
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.acquire(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquire is a continuation of Acquire function that doesn't check context
|
||||||
|
// validity.
|
||||||
|
//
|
||||||
|
// This function exists solely only for benchmarking purposes.
|
||||||
|
func (p *Pool[T]) acquire(ctx context.Context) (*Resource[T], error) {
|
||||||
|
startNano := nanotime()
|
||||||
|
|
||||||
|
var waitedForLock bool
|
||||||
|
if !p.acquireSem.TryAcquire(1) {
|
||||||
|
waitedForLock = true
|
||||||
|
err := p.acquireSem.Acquire(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
p.canceledAcquireCount.Add(1)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
if p.closed {
|
||||||
|
p.acquireSem.Release(1)
|
||||||
|
p.mux.Unlock()
|
||||||
|
return nil, ErrClosedPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a resource is available in the pool.
|
||||||
|
if res := p.tryAcquireIdleResource(); res != nil {
|
||||||
|
if waitedForLock {
|
||||||
|
p.emptyAcquireCount += 1
|
||||||
|
}
|
||||||
|
p.acquireCount += 1
|
||||||
|
p.acquireDuration += time.Duration(nanotime() - startNano)
|
||||||
|
p.mux.Unlock()
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.allResources) >= int(p.maxSize) {
|
||||||
|
// Unreachable code.
|
||||||
|
panic("bug: semaphore allowed more acquires than pool allows")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resource is not idle, but there is enough space to create one.
|
||||||
|
res := p.createNewResource()
|
||||||
|
p.mux.Unlock()
|
||||||
|
|
||||||
|
res, err := p.initResourceValue(ctx, res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
p.emptyAcquireCount += 1
|
||||||
|
p.acquireCount += 1
|
||||||
|
p.acquireDuration += time.Duration(nanotime() - startNano)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) initResourceValue(ctx context.Context, res *Resource[T]) (*Resource[T], error) {
|
||||||
|
// Create the resource in a goroutine to immediately return from Acquire
|
||||||
|
// if ctx is canceled without also canceling the constructor.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// - https://github.com/jackc/pgx/issues/1287
|
||||||
|
// - https://github.com/jackc/pgx/issues/1259
|
||||||
|
constructErrChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
constructorCtx := newValueCancelCtx(ctx, p.baseAcquireCtx)
|
||||||
|
value, err := p.constructor(constructorCtx)
|
||||||
|
if err != nil {
|
||||||
|
p.mux.Lock()
|
||||||
|
p.allResources.remove(res)
|
||||||
|
p.destructWG.Done()
|
||||||
|
|
||||||
|
// The resource won't be acquired because its
|
||||||
|
// construction failed. We have to allow someone else to
|
||||||
|
// take that resouce.
|
||||||
|
p.acquireSem.Release(1)
|
||||||
|
p.mux.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case constructErrChan <- err:
|
||||||
|
case <-ctx.Done():
|
||||||
|
// The caller is cancelled, so no-one awaits the
|
||||||
|
// error. This branch avoid goroutine leak.
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resource is already in p.allResources where it might be read. So we need to acquire the lock to update its
|
||||||
|
// status.
|
||||||
|
p.mux.Lock()
|
||||||
|
res.value = value
|
||||||
|
res.status = resourceStatusAcquired
|
||||||
|
p.mux.Unlock()
|
||||||
|
|
||||||
|
// This select works because the channel is unbuffered.
|
||||||
|
select {
|
||||||
|
case constructErrChan <- nil:
|
||||||
|
case <-ctx.Done():
|
||||||
|
p.releaseAcquiredResource(res, res.lastUsedNano)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
p.canceledAcquireCount.Add(1)
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case err := <-constructErrChan:
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryAcquire gets a resource from the pool if one is immediately available. If not, it returns ErrNotAvailable. If no
|
||||||
|
// resources are available but the pool has room to grow, a resource will be created in the background. ctx is only
|
||||||
|
// used to cancel the background creation.
|
||||||
|
func (p *Pool[T]) TryAcquire(ctx context.Context) (*Resource[T], error) {
|
||||||
|
if !p.acquireSem.TryAcquire(1) {
|
||||||
|
return nil, ErrNotAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
p.acquireSem.Release(1)
|
||||||
|
return nil, ErrClosedPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a resource is available now
|
||||||
|
if res := p.tryAcquireIdleResource(); res != nil {
|
||||||
|
p.acquireCount += 1
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.allResources) >= int(p.maxSize) {
|
||||||
|
// Unreachable code.
|
||||||
|
panic("bug: semaphore allowed more acquires than pool allows")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := p.createNewResource()
|
||||||
|
go func() {
|
||||||
|
value, err := p.constructor(ctx)
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
// We have to create the resource and only then release the
|
||||||
|
// semaphore - For the time being there is no resource that
|
||||||
|
// someone could acquire.
|
||||||
|
defer p.acquireSem.Release(1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
p.allResources.remove(res)
|
||||||
|
p.destructWG.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.value = value
|
||||||
|
res.status = resourceStatusIdle
|
||||||
|
p.idleResources.Push(res)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil, ErrNotAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquireSemAll tries to acquire num free tokens from sem. This function is
|
||||||
|
// guaranteed to acquire at least the lowest number of tokens that has been
|
||||||
|
// available in the semaphore during runtime of this function.
|
||||||
|
//
|
||||||
|
// For the time being, semaphore doesn't allow to acquire all tokens atomically
|
||||||
|
// (see https://github.com/golang/sync/pull/19). We simulate this by trying all
|
||||||
|
// powers of 2 that are less or equal to num.
|
||||||
|
//
|
||||||
|
// For example, let's immagine we have 19 free tokens in the semaphore which in
|
||||||
|
// total has 24 tokens (i.e. the maxSize of the pool is 24 resources). Then if
|
||||||
|
// num is 24, the log2Uint(24) is 4 and we try to acquire 16, 8, 4, 2 and 1
|
||||||
|
// tokens. Out of those, the acquire of 16, 2 and 1 tokens will succeed.
|
||||||
|
//
|
||||||
|
// Naturally, Acquires and Releases of the semaphore might take place
|
||||||
|
// concurrently. For this reason, it's not guaranteed that absolutely all free
|
||||||
|
// tokens in the semaphore will be acquired. But it's guaranteed that at least
|
||||||
|
// the minimal number of tokens that has been present over the whole process
|
||||||
|
// will be acquired. This is sufficient for the use-case we have in this
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
// TODO: Replace this with acquireSem.TryAcquireAll() if it gets to
|
||||||
|
// upstream. https://github.com/golang/sync/pull/19
|
||||||
|
func acquireSemAll(sem *semaphore.Weighted, num int) int {
|
||||||
|
if sem.TryAcquire(int64(num)) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
var acquired int
|
||||||
|
for i := int(log2Int(num)); i >= 0; i-- {
|
||||||
|
val := 1 << i
|
||||||
|
if sem.TryAcquire(int64(val)) {
|
||||||
|
acquired += val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acquired
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireAllIdle acquires all currently idle resources. Its intended use is for
|
||||||
|
// health check and keep-alive functionality. It does not update pool
|
||||||
|
// statistics.
|
||||||
|
func (p *Pool[T]) AcquireAllIdle() []*Resource[T] {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numIdle := p.idleResources.Len()
|
||||||
|
if numIdle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In acquireSemAll we use only TryAcquire and not Acquire. Because
|
||||||
|
// TryAcquire cannot block, the fact that we hold mutex locked and try
|
||||||
|
// to acquire semaphore cannot result in dead-lock.
|
||||||
|
//
|
||||||
|
// Because the mutex is locked, no parallel Release can run. This
|
||||||
|
// implies that the number of tokens can only decrease because some
|
||||||
|
// Acquire/TryAcquire call can consume the semaphore token. Consequently
|
||||||
|
// acquired is always less or equal to numIdle. Moreover if acquired <
|
||||||
|
// numIdle, then there are some parallel Acquire/TryAcquire calls that
|
||||||
|
// will take the remaining idle connections.
|
||||||
|
acquired := acquireSemAll(p.acquireSem, numIdle)
|
||||||
|
|
||||||
|
idle := make([]*Resource[T], acquired)
|
||||||
|
for i := range idle {
|
||||||
|
res, _ := p.idleResources.Pop()
|
||||||
|
res.status = resourceStatusAcquired
|
||||||
|
idle[i] = res
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to bump the generation to ensure that Acquire/TryAcquire
|
||||||
|
// calls running in parallel (those which caused acquired < numIdle)
|
||||||
|
// will consume old connections and not freshly released connections
|
||||||
|
// instead.
|
||||||
|
p.idleResources.NextGen()
|
||||||
|
|
||||||
|
return idle
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResource constructs a new resource without acquiring it. It goes straight in the IdlePool. If the pool is full
|
||||||
|
// it returns an error. It can be useful to maintain warm resources under little load.
|
||||||
|
func (p *Pool[T]) CreateResource(ctx context.Context) error {
|
||||||
|
if !p.acquireSem.TryAcquire(1) {
|
||||||
|
return ErrNotAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
if p.closed {
|
||||||
|
p.acquireSem.Release(1)
|
||||||
|
p.mux.Unlock()
|
||||||
|
return ErrClosedPool
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.allResources) >= int(p.maxSize) {
|
||||||
|
p.acquireSem.Release(1)
|
||||||
|
p.mux.Unlock()
|
||||||
|
return ErrNotAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
res := p.createNewResource()
|
||||||
|
p.mux.Unlock()
|
||||||
|
|
||||||
|
value, err := p.constructor(ctx)
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
defer p.acquireSem.Release(1)
|
||||||
|
if err != nil {
|
||||||
|
p.allResources.remove(res)
|
||||||
|
p.destructWG.Done()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.value = value
|
||||||
|
res.status = resourceStatusIdle
|
||||||
|
|
||||||
|
// If closed while constructing resource then destroy it and return an error
|
||||||
|
if p.closed {
|
||||||
|
go p.destructResourceValue(res.value)
|
||||||
|
return ErrClosedPool
|
||||||
|
}
|
||||||
|
|
||||||
|
p.idleResources.Push(res)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset destroys all resources, but leaves the pool open. It is intended for use when an error is detected that would
|
||||||
|
// disrupt all resources (such as a network interruption or a server state change).
|
||||||
|
//
|
||||||
|
// It is safe to reset a pool while resources are checked out. Those resources will be destroyed when they are returned
|
||||||
|
// to the pool.
|
||||||
|
func (p *Pool[T]) Reset() {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
|
||||||
|
p.resetCount++
|
||||||
|
|
||||||
|
for res, ok := p.idleResources.Pop(); ok; res, ok = p.idleResources.Pop() {
|
||||||
|
p.allResources.remove(res)
|
||||||
|
go p.destructResourceValue(res.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// releaseAcquiredResource returns res to the the pool.
|
||||||
|
func (p *Pool[T]) releaseAcquiredResource(res *Resource[T], lastUsedNano int64) {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
defer p.acquireSem.Release(1)
|
||||||
|
|
||||||
|
if p.closed || res.poolResetCount != p.resetCount {
|
||||||
|
p.allResources.remove(res)
|
||||||
|
go p.destructResourceValue(res.value)
|
||||||
|
} else {
|
||||||
|
res.lastUsedNano = lastUsedNano
|
||||||
|
res.status = resourceStatusIdle
|
||||||
|
p.idleResources.Push(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes res from the pool and closes it. If res is not part of the
|
||||||
|
// pool Remove will panic.
|
||||||
|
func (p *Pool[T]) destroyAcquiredResource(res *Resource[T]) {
|
||||||
|
p.destructResourceValue(res.value)
|
||||||
|
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
defer p.acquireSem.Release(1)
|
||||||
|
|
||||||
|
p.allResources.remove(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) hijackAcquiredResource(res *Resource[T]) {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
|
defer p.acquireSem.Release(1)
|
||||||
|
|
||||||
|
p.allResources.remove(res)
|
||||||
|
res.status = resourceStatusHijacked
|
||||||
|
p.destructWG.Done() // not responsible for destructing hijacked resources
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) destructResourceValue(value T) {
|
||||||
|
p.destructor(value)
|
||||||
|
p.destructWG.Done()
|
||||||
|
}
|
28
vendor/github.com/jackc/puddle/v2/resource_list.go
generated
vendored
Normal file
28
vendor/github.com/jackc/puddle/v2/resource_list.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package puddle
|
||||||
|
|
||||||
|
type resList[T any] []*Resource[T]
|
||||||
|
|
||||||
|
func (l *resList[T]) append(val *Resource[T]) { *l = append(*l, val) }
|
||||||
|
|
||||||
|
func (l *resList[T]) popBack() *Resource[T] {
|
||||||
|
idx := len(*l) - 1
|
||||||
|
val := (*l)[idx]
|
||||||
|
(*l)[idx] = nil // Avoid memory leak
|
||||||
|
*l = (*l)[:idx]
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *resList[T]) remove(val *Resource[T]) {
|
||||||
|
for i, elem := range *l {
|
||||||
|
if elem == val {
|
||||||
|
lastIdx := len(*l) - 1
|
||||||
|
(*l)[i] = (*l)[lastIdx]
|
||||||
|
(*l)[lastIdx] = nil // Avoid memory leak
|
||||||
|
(*l) = (*l)[:lastIdx]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("BUG: removeResource could not find res in slice")
|
||||||
|
}
|
27
vendor/golang.org/x/sync/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/sync/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/sync/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/sync/PATENTS
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
136
vendor/golang.org/x/sync/semaphore/semaphore.go
generated
vendored
Normal file
136
vendor/golang.org/x/sync/semaphore/semaphore.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package semaphore provides a weighted semaphore implementation.
|
||||||
|
package semaphore // import "golang.org/x/sync/semaphore"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type waiter struct {
|
||||||
|
n int64
|
||||||
|
ready chan<- struct{} // Closed when semaphore acquired.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWeighted creates a new weighted semaphore with the given
|
||||||
|
// maximum combined weight for concurrent access.
|
||||||
|
func NewWeighted(n int64) *Weighted {
|
||||||
|
w := &Weighted{size: n}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weighted provides a way to bound concurrent access to a resource.
|
||||||
|
// The callers can request access with a given weight.
|
||||||
|
type Weighted struct {
|
||||||
|
size int64
|
||||||
|
cur int64
|
||||||
|
mu sync.Mutex
|
||||||
|
waiters list.List
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire acquires the semaphore with a weight of n, blocking until resources
|
||||||
|
// are available or ctx is done. On success, returns nil. On failure, returns
|
||||||
|
// ctx.Err() and leaves the semaphore unchanged.
|
||||||
|
//
|
||||||
|
// If ctx is already done, Acquire may still succeed without blocking.
|
||||||
|
func (s *Weighted) Acquire(ctx context.Context, n int64) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.size-s.cur >= n && s.waiters.Len() == 0 {
|
||||||
|
s.cur += n
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > s.size {
|
||||||
|
// Don't make other Acquire calls block on one that's doomed to fail.
|
||||||
|
s.mu.Unlock()
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
ready := make(chan struct{})
|
||||||
|
w := waiter{n: n, ready: ready}
|
||||||
|
elem := s.waiters.PushBack(w)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := ctx.Err()
|
||||||
|
s.mu.Lock()
|
||||||
|
select {
|
||||||
|
case <-ready:
|
||||||
|
// Acquired the semaphore after we were canceled. Rather than trying to
|
||||||
|
// fix up the queue, just pretend we didn't notice the cancelation.
|
||||||
|
err = nil
|
||||||
|
default:
|
||||||
|
isFront := s.waiters.Front() == elem
|
||||||
|
s.waiters.Remove(elem)
|
||||||
|
// If we're at the front and there're extra tokens left, notify other waiters.
|
||||||
|
if isFront && s.size > s.cur {
|
||||||
|
s.notifyWaiters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
return err
|
||||||
|
|
||||||
|
case <-ready:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryAcquire acquires the semaphore with a weight of n without blocking.
|
||||||
|
// On success, returns true. On failure, returns false and leaves the semaphore unchanged.
|
||||||
|
func (s *Weighted) TryAcquire(n int64) bool {
|
||||||
|
s.mu.Lock()
|
||||||
|
success := s.size-s.cur >= n && s.waiters.Len() == 0
|
||||||
|
if success {
|
||||||
|
s.cur += n
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the semaphore with a weight of n.
|
||||||
|
func (s *Weighted) Release(n int64) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.cur -= n
|
||||||
|
if s.cur < 0 {
|
||||||
|
s.mu.Unlock()
|
||||||
|
panic("semaphore: released more than held")
|
||||||
|
}
|
||||||
|
s.notifyWaiters()
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Weighted) notifyWaiters() {
|
||||||
|
for {
|
||||||
|
next := s.waiters.Front()
|
||||||
|
if next == nil {
|
||||||
|
break // No more waiters blocked.
|
||||||
|
}
|
||||||
|
|
||||||
|
w := next.Value.(waiter)
|
||||||
|
if s.size-s.cur < w.n {
|
||||||
|
// Not enough tokens for the next waiter. We could keep going (to try to
|
||||||
|
// find a waiter with a smaller request), but under load that could cause
|
||||||
|
// starvation for large requests; instead, we leave all remaining waiters
|
||||||
|
// blocked.
|
||||||
|
//
|
||||||
|
// Consider a semaphore used as a read-write lock, with N tokens, N
|
||||||
|
// readers, and one writer. Each reader can Acquire(1) to obtain a read
|
||||||
|
// lock. The writer can Acquire(N) to obtain a write lock, excluding all
|
||||||
|
// of the readers. If we allow the readers to jump ahead in the queue,
|
||||||
|
// the writer will starve — there is always one token available for every
|
||||||
|
// reader.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cur += w.n
|
||||||
|
s.waiters.Remove(next)
|
||||||
|
close(w.ready)
|
||||||
|
}
|
||||||
|
}
|
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
|
@ -315,7 +315,7 @@ github.com/jackc/pgpassfile
|
||||||
# github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a
|
# github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/jackc/pgservicefile
|
github.com/jackc/pgservicefile
|
||||||
# github.com/jackc/pgx/v5 v5.4.3
|
# github.com/jackc/pgx/v5 v5.5.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/jackc/pgx/v5
|
github.com/jackc/pgx/v5
|
||||||
github.com/jackc/pgx/v5/internal/anynil
|
github.com/jackc/pgx/v5/internal/anynil
|
||||||
|
@ -328,7 +328,12 @@ github.com/jackc/pgx/v5/pgconn/internal/bgreader
|
||||||
github.com/jackc/pgx/v5/pgconn/internal/ctxwatch
|
github.com/jackc/pgx/v5/pgconn/internal/ctxwatch
|
||||||
github.com/jackc/pgx/v5/pgproto3
|
github.com/jackc/pgx/v5/pgproto3
|
||||||
github.com/jackc/pgx/v5/pgtype
|
github.com/jackc/pgx/v5/pgtype
|
||||||
|
github.com/jackc/pgx/v5/pgxpool
|
||||||
github.com/jackc/pgx/v5/stdlib
|
github.com/jackc/pgx/v5/stdlib
|
||||||
|
# github.com/jackc/puddle/v2 v2.2.1
|
||||||
|
## explicit; go 1.19
|
||||||
|
github.com/jackc/puddle/v2
|
||||||
|
github.com/jackc/puddle/v2/internal/genstack
|
||||||
# github.com/jinzhu/inflection v1.0.0
|
# github.com/jinzhu/inflection v1.0.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/jinzhu/inflection
|
github.com/jinzhu/inflection
|
||||||
|
@ -851,6 +856,9 @@ golang.org/x/net/trace
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/oauth2
|
golang.org/x/oauth2
|
||||||
golang.org/x/oauth2/internal
|
golang.org/x/oauth2/internal
|
||||||
|
# golang.org/x/sync v0.3.0
|
||||||
|
## explicit; go 1.17
|
||||||
|
golang.org/x/sync/semaphore
|
||||||
# golang.org/x/sys v0.13.0
|
# golang.org/x/sys v0.13.0
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
golang.org/x/sys/cpu
|
golang.org/x/sys/cpu
|
||||||
|
|
Loading…
Reference in a new issue