Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const sql = postgres('postgres://username:password@host:port/database', {
password : '', // Password of database user
ssl : false, // true, prefer, require, tls.connect options
max : 10, // Max number of connections
lifetime : -1, // Max connection lifetime in seconds (default: forever)
idle_timeout : 0, // Idle connection timeout in seconds
connect_timeout : 30, // Connect timeout in seconds
no_prepare : false, // No automatic creation of prepared statements
Expand Down
30 changes: 29 additions & 1 deletion lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function Connection(options = {}) {
transform,
idle_timeout,
connect_timeout,
lifetime,
onnotify,
onnotice,
onclose,
Expand All @@ -31,6 +32,9 @@ function Connection(options = {}) {
let ready = false
let write = false
let next = false
let readyAt
let endingGracefully
let endedGracefully
let statements = {}
let connect_timer

Expand All @@ -46,7 +50,7 @@ function Connection(options = {}) {
cleanup
})

const connection = { send, end, destroy, socket }
const connection = { send, hasExpired, endGracefully, end, destroy, socket }

const backend = Backend({
onparse,
Expand Down Expand Up @@ -105,6 +109,21 @@ function Connection(options = {}) {
).catch(onerror)
}

function hasExpired() {
return lifetime > 0 && ((new Date() - readyAt) > (lifetime * 1000))
}

function endGracefully() {
if (!endingGracefully) {
endingGracefully = new Promise((resolve) => {
endedGracefully = () => resolve()
})
}
tryGracefulEnd()

return endingGracefully
}

function end() {
clearTimeout(timer)
const promise = new Promise((resolve) => {
Expand Down Expand Up @@ -207,6 +226,11 @@ function Connection(options = {}) {
clearTimeout(timer)
timer = setTimeout(socket.end, idle_timeout * 1000)
}
endingGracefully && tryGracefulEnd()
}

function tryGracefulEnd() {
!backend.query && queries.length === 0 && end().then(destroy).then(endedGracefully)
}

function onready(err) {
Expand Down Expand Up @@ -243,6 +267,10 @@ function Connection(options = {}) {
messages.forEach(x => socket.write(x))
messages = []
open = true

if (lifetime > -1) {
readyAt = new Date()
}
}

backend.query = queries.shift()
Expand Down
17 changes: 16 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,21 @@ function Postgres(a, b) {
}

function getConnection(reserve) {
const connection = slots ? createConnection(options) : connections.shift()
let connection
while (1) {
connection = slots ? createConnection(options) : connections.shift()
if (!connection || !connection.hasExpired())
break

// has expired
const conn = connection // local scope closure
conn.endGracefully().then(() => {
slots++
// remove connection from all
all.splice(all.findIndex(x => x === conn), 1)
})
}

!reserve && connection && connections.push(connection)
return connection
}
Expand Down Expand Up @@ -634,6 +648,7 @@ function parseOptions(a, b) {
user : user,
pass : o.pass || o.password || auth[1] || env.PGPASSWORD || '',
max : o.max || url.query.max || 10,
lifetime : o.lifetime || -1,
types : o.types || {},
ssl : o.ssl || parseSSL(url.query.sslmode || url.query.ssl) || false,
idle_timeout : o.idle_timeout || url.query.idle_timeout || env.PGIDLE_TIMEOUT || warn(o.timeout),
Expand Down
46 changes: 46 additions & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1494,3 +1494,49 @@ t('multiple queries before connect', async() => {
xs.map(x => x[0].x).join()
]
})

t('Connection with no lifetime', async() => {
const sql = postgres(options)

let result = await sql`select 1`
let startPid = result.state.pid

await delay(100);

result = await sql`select 1`
let pid = result.state.pid

return [true, pid == startPid]
})

t('Connection lifetime', async() => {
const sql = postgres({...options, lifetime: 0.050})

let result = await sql`select 1`
let startPid = result.state.pid

await delay(100);

result = await sql`select 1`
let pid = result.state.pid

return [true, pid != startPid]
})

t('Connection lifetime transaction', async() => {
const sql = postgres({...options, lifetime: 0.050})

let success

await sql.begin(async (sql) => {
await delay(100);
try {
await sql`select 1`
success = true
} catch (err) {
success = false
}
})

return [true, success]
})
2 changes: 2 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ interface BaseOptions<T extends JSToPostgresTypeMap> {
ssl: 'require' | 'prefer' | boolean | object;
/** Max number of connections */
max: number;
/** Connection lifetime in seconds */
lifetime: number | undefined;
/** Idle connection timeout in seconds */
idle_timeout: number | undefined;
/** Connect timeout in seconds */
Expand Down