From 5082e30c4c1e6ebefe695c276032d35dbb31f8b5 Mon Sep 17 00:00:00 2001 From: Patrick Ellul Date: Tue, 28 Jan 2020 16:36:34 +1100 Subject: [PATCH] Implement maximum connection idle time (MaxConnIdleTime) for pgxpool. If a connection has been idle for longer than maxConnIdleTime, the health check will destroy it. --- go.mod | 2 +- go.sum | 2 ++ pgxpool/pool.go | 23 +++++++++++++++++++++-- pgxpool/pool_test.go | 23 +++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 040ba170..9b854033 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/jackc/pgio v1.0.0 github.com/jackc/pgproto3/v2 v2.0.1 github.com/jackc/pgtype v1.1.0 - github.com/jackc/puddle v1.0.0 + github.com/jackc/puddle v1.0.1-0.20200126004755-807afe48a83d github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.9 // indirect github.com/rs/zerolog v1.15.0 diff --git a/go.sum b/go.sum index 7b10e54d..20c51f28 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9 h1:KLBBPU++1T3DHtm1B1 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.0.0 h1:rbjAshlgKscNa7j0jAM0uNQflis5o2XUogPMVAwtcsM= github.com/jackc/puddle v1.0.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.0.1-0.20200126004755-807afe48a83d h1:lYLhmugF2D1ysJgU4pyW/GcdH+X4O3T96duzNdxcHqY= +github.com/jackc/puddle v1.0.1-0.20200126004755-807afe48a83d/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= diff --git a/pgxpool/pool.go b/pgxpool/pool.go index dd5a48b1..22a09e67 100644 --- a/pgxpool/pool.go +++ b/pgxpool/pool.go @@ -14,6 +14,7 @@ import ( var defaultMaxConns = int32(4) var defaultMaxConnLifetime = time.Hour +var defaultMaxConnIdleTime = time.Minute * 30 var defaultHealthCheckPeriod = time.Minute type connResource struct { @@ -71,6 +72,7 @@ type Pool struct { beforeAcquire func(context.Context, *pgx.Conn) bool afterRelease func(*pgx.Conn) bool maxConnLifetime time.Duration + maxConnIdleTime time.Duration healthCheckPeriod time.Duration closeChan chan struct{} } @@ -92,9 +94,12 @@ type Config struct { // return the connection to the pool or false to destroy the connection. AfterRelease func(*pgx.Conn) bool - // MaxConnLifetime is the duration after which a connection will be automatically closed. + // MaxConnLifetime is the duration since creation after which a connection will be automatically closed. MaxConnLifetime 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. MaxConns int32 @@ -129,6 +134,7 @@ func ConnectConfig(ctx context.Context, config *Config) (*Pool, error) { beforeAcquire: config.BeforeAcquire, afterRelease: config.AfterRelease, maxConnLifetime: config.MaxConnLifetime, + maxConnIdleTime: config.MaxConnIdleTime, healthCheckPeriod: config.HealthCheckPeriod, closeChan: make(chan struct{}), } @@ -233,6 +239,17 @@ func ParseConfig(connString string) (*Config, error) { 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, errors.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) @@ -275,8 +292,10 @@ func (p *Pool) checkIdleConnsHealth() { for _, res := range resources { if now.Sub(res.CreationTime()) > p.maxConnLifetime { res.Destroy() + } else if res.IdleDuration() > p.maxConnIdleTime { + res.Destroy() } else { - res.Release() + res.ReleaseUnused() } } } diff --git a/pgxpool/pool_test.go b/pgxpool/pool_test.go index bc02b0bf..d8ac4f98 100644 --- a/pgxpool/pool_test.go +++ b/pgxpool/pool_test.go @@ -253,6 +253,29 @@ func TestPoolBackgroundChecksMaxConnLifetime(t *testing.T) { assert.EqualValues(t, 0, stats.TotalConns()) } +func TestPoolBackgroundChecksMaxConnIdleTime(t *testing.T) { + t.Parallel() + + config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) + require.NoError(t, err) + + config.MaxConnLifetime = 1 * time.Minute + config.MaxConnIdleTime = 100 * time.Millisecond + config.HealthCheckPeriod = 150 * time.Millisecond + + db, err := pgxpool.ConnectConfig(context.Background(), config) + require.NoError(t, err) + defer db.Close() + + c, err := db.Acquire(context.Background()) + require.NoError(t, err) + c.Release() + time.Sleep(config.HealthCheckPeriod + 50*time.Millisecond) + + stats := db.Stat() + assert.EqualValues(t, 0, stats.TotalConns()) +} + func TestPoolExec(t *testing.T) { t.Parallel()