Skip to content

Commit 5b773ce

Browse files
committed
Add onidle callback option to fix #1100
1 parent 5c8135f commit 5b773ce

File tree

8 files changed

+317
-3
lines changed

8 files changed

+317
-3
lines changed

cjs/src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ function Postgres(a, b) {
310310
queue.push(c)
311311
c.queue = queue
312312
queue === open
313-
? c.idleTimer.start()
313+
? (c.idleTimer.start(), options.onidle && options.onidle(c.id))
314314
: c.idleTimer.cancel()
315315
return c
316316
}
@@ -492,6 +492,7 @@ function parseOptions(a, b) {
492492
onnotify : o.onnotify,
493493
onclose : o.onclose,
494494
onparameter : o.onparameter,
495+
onidle : o.onidle,
495496
socket : o.socket,
496497
transform : parseTransform(o.transform || { undefined: undefined }),
497498
parameters : {},

cjs/tests/index.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,107 @@ t('responds with server parameters (application_name)', async() =>
959959
})`select 1`.catch(reject))]
960960
)
961961

962+
t('onidle fires for each connection in pool', async() => {
963+
const ids = []
964+
const sql = postgres({
965+
...options,
966+
max: 2,
967+
onidle: (id) => ids.push(id)
968+
})
969+
970+
// Run two queries concurrently to use both connections.
971+
await Promise.all([
972+
sql`select pg_sleep(0.05)`,
973+
sql`select pg_sleep(0.05)`
974+
])
975+
await sql.end()
976+
977+
// Both connections should have fired onidle.
978+
return [2, ids.length]
979+
})
980+
981+
t('onidle not called when queries are queued', async() => {
982+
let idleCount = 0
983+
const sql = postgres({
984+
...options,
985+
max: 1,
986+
onidle: () => idleCount++
987+
})
988+
989+
// Run two queries concurrently on single connection.
990+
await Promise.all([
991+
sql`select pg_sleep(0.05)`,
992+
sql`select 1`
993+
])
994+
await sql.end()
995+
996+
// Should only fire once after both queries complete and connection returns to idle.
997+
return [1, idleCount]
998+
})
999+
1000+
t('onidle receives connection id', async() => {
1001+
const ids = []
1002+
const sql = postgres({
1003+
...options,
1004+
max: 2,
1005+
onidle: (id) => ids.push(id)
1006+
})
1007+
1008+
// Use two connections.
1009+
await Promise.all([
1010+
sql`select pg_sleep(0.05)`,
1011+
sql`select pg_sleep(0.05)`
1012+
])
1013+
await sql.end()
1014+
1015+
// Should receive valid integer ids, and they should be different.
1016+
return [true, ids.length === 2 && ids.every(Number.isInteger) && ids[0] !== ids[1]]
1017+
})
1018+
1019+
t('onidle can be set after creation', async() => {
1020+
let called = false
1021+
const sql = postgres({ ...options, max: 1 })
1022+
1023+
sql.options.onidle = () => called = true
1024+
1025+
await sql`select 1`
1026+
await sql.end()
1027+
1028+
return [true, called]
1029+
})
1030+
1031+
t('onidle fires after reserved connection is released', async() => {
1032+
let idleCount = 0
1033+
const sql = postgres({
1034+
...options,
1035+
max: 1,
1036+
onidle: () => idleCount++
1037+
})
1038+
1039+
const reserved = await sql.reserve()
1040+
await reserved`select 1`
1041+
reserved.release()
1042+
await sql.end()
1043+
1044+
return [1, idleCount]
1045+
})
1046+
1047+
t('onidle fires after transaction completes', async() => {
1048+
let idleCount = 0
1049+
const sql = postgres({
1050+
...options,
1051+
max: 1,
1052+
onidle: () => idleCount++
1053+
})
1054+
1055+
await sql.begin(async sql => {
1056+
await sql`select 1`
1057+
})
1058+
await sql.end()
1059+
1060+
return [1, idleCount]
1061+
})
1062+
9621063
t('has server parameters', async() => {
9631064
return ['postgres.js', (await sql`select 1`.then(() => sql.parameters.application_name))]
9641065
})

deno/src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ function Postgres(a, b) {
311311
queue.push(c)
312312
c.queue = queue
313313
queue === open
314-
? c.idleTimer.start()
314+
? (c.idleTimer.start(), options.onidle && options.onidle(c.id))
315315
: c.idleTimer.cancel()
316316
return c
317317
}
@@ -493,6 +493,7 @@ function parseOptions(a, b) {
493493
onnotify : o.onnotify,
494494
onclose : o.onclose,
495495
onparameter : o.onparameter,
496+
onidle : o.onidle,
496497
socket : o.socket,
497498
transform : parseTransform(o.transform || { undefined: undefined }),
498499
parameters : {},

deno/tests/index.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,107 @@ t('responds with server parameters (application_name)', async() =>
961961
})`select 1`.catch(reject))]
962962
)
963963

964+
t('onidle fires for each connection in pool', async() => {
965+
const ids = []
966+
const sql = postgres({
967+
...options,
968+
max: 2,
969+
onidle: (id) => ids.push(id)
970+
})
971+
972+
// Run two queries concurrently to use both connections.
973+
await Promise.all([
974+
sql`select pg_sleep(0.05)`,
975+
sql`select pg_sleep(0.05)`
976+
])
977+
await sql.end()
978+
979+
// Both connections should have fired onidle.
980+
return [2, ids.length]
981+
})
982+
983+
t('onidle not called when queries are queued', async() => {
984+
let idleCount = 0
985+
const sql = postgres({
986+
...options,
987+
max: 1,
988+
onidle: () => idleCount++
989+
})
990+
991+
// Run two queries concurrently on single connection.
992+
await Promise.all([
993+
sql`select pg_sleep(0.05)`,
994+
sql`select 1`
995+
])
996+
await sql.end()
997+
998+
// Should only fire once after both queries complete and connection returns to idle.
999+
return [1, idleCount]
1000+
})
1001+
1002+
t('onidle receives connection id', async() => {
1003+
const ids = []
1004+
const sql = postgres({
1005+
...options,
1006+
max: 2,
1007+
onidle: (id) => ids.push(id)
1008+
})
1009+
1010+
// Use two connections.
1011+
await Promise.all([
1012+
sql`select pg_sleep(0.05)`,
1013+
sql`select pg_sleep(0.05)`
1014+
])
1015+
await sql.end()
1016+
1017+
// Should receive valid integer ids, and they should be different.
1018+
return [true, ids.length === 2 && ids.every(Number.isInteger) && ids[0] !== ids[1]]
1019+
})
1020+
1021+
t('onidle can be set after creation', async() => {
1022+
let called = false
1023+
const sql = postgres({ ...options, max: 1 })
1024+
1025+
sql.options.onidle = () => called = true
1026+
1027+
await sql`select 1`
1028+
await sql.end()
1029+
1030+
return [true, called]
1031+
})
1032+
1033+
t('onidle fires after reserved connection is released', async() => {
1034+
let idleCount = 0
1035+
const sql = postgres({
1036+
...options,
1037+
max: 1,
1038+
onidle: () => idleCount++
1039+
})
1040+
1041+
const reserved = await sql.reserve()
1042+
await reserved`select 1`
1043+
reserved.release()
1044+
await sql.end()
1045+
1046+
return [1, idleCount]
1047+
})
1048+
1049+
t('onidle fires after transaction completes', async() => {
1050+
let idleCount = 0
1051+
const sql = postgres({
1052+
...options,
1053+
max: 1,
1054+
onidle: () => idleCount++
1055+
})
1056+
1057+
await sql.begin(async sql => {
1058+
await sql`select 1`
1059+
})
1060+
await sql.end()
1061+
1062+
return [1, idleCount]
1063+
})
1064+
9641065
t('has server parameters', async() => {
9651066
return ['postgres.js', (await sql`select 1`.then(() => sql.parameters.application_name))]
9661067
})

deno/types/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
121121
*/
122122
publications: string
123123
onclose: (connId: number) => void;
124+
/**
125+
* Called when a connection returns to the pool after completing queries
126+
*/
127+
onidle: (connId: number) => void;
124128
backoff: boolean | ((attemptNum: number) => number);
125129
max_lifetime: number | null;
126130
keep_alive: number | null;

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ function Postgres(a, b) {
310310
queue.push(c)
311311
c.queue = queue
312312
queue === open
313-
? c.idleTimer.start()
313+
? (c.idleTimer.start(), options.onidle && options.onidle(c.id))
314314
: c.idleTimer.cancel()
315315
return c
316316
}
@@ -492,6 +492,7 @@ function parseOptions(a, b) {
492492
onnotify : o.onnotify,
493493
onclose : o.onclose,
494494
onparameter : o.onparameter,
495+
onidle : o.onidle,
495496
socket : o.socket,
496497
transform : parseTransform(o.transform || { undefined: undefined }),
497498
parameters : {},

tests/index.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,107 @@ t('responds with server parameters (application_name)', async() =>
959959
})`select 1`.catch(reject))]
960960
)
961961

962+
t('onidle fires for each connection in pool', async() => {
963+
const ids = []
964+
const sql = postgres({
965+
...options,
966+
max: 2,
967+
onidle: (id) => ids.push(id)
968+
})
969+
970+
// Run two queries concurrently to use both connections.
971+
await Promise.all([
972+
sql`select pg_sleep(0.05)`,
973+
sql`select pg_sleep(0.05)`
974+
])
975+
await sql.end()
976+
977+
// Both connections should have fired onidle.
978+
return [2, ids.length]
979+
})
980+
981+
t('onidle not called when queries are queued', async() => {
982+
let idleCount = 0
983+
const sql = postgres({
984+
...options,
985+
max: 1,
986+
onidle: () => idleCount++
987+
})
988+
989+
// Run two queries concurrently on single connection.
990+
await Promise.all([
991+
sql`select pg_sleep(0.05)`,
992+
sql`select 1`
993+
])
994+
await sql.end()
995+
996+
// Should only fire once after both queries complete and connection returns to idle.
997+
return [1, idleCount]
998+
})
999+
1000+
t('onidle receives connection id', async() => {
1001+
const ids = []
1002+
const sql = postgres({
1003+
...options,
1004+
max: 2,
1005+
onidle: (id) => ids.push(id)
1006+
})
1007+
1008+
// Use two connections.
1009+
await Promise.all([
1010+
sql`select pg_sleep(0.05)`,
1011+
sql`select pg_sleep(0.05)`
1012+
])
1013+
await sql.end()
1014+
1015+
// Should receive valid integer ids, and they should be different.
1016+
return [true, ids.length === 2 && ids.every(Number.isInteger) && ids[0] !== ids[1]]
1017+
})
1018+
1019+
t('onidle can be set after creation', async() => {
1020+
let called = false
1021+
const sql = postgres({ ...options, max: 1 })
1022+
1023+
sql.options.onidle = () => called = true
1024+
1025+
await sql`select 1`
1026+
await sql.end()
1027+
1028+
return [true, called]
1029+
})
1030+
1031+
t('onidle fires after reserved connection is released', async() => {
1032+
let idleCount = 0
1033+
const sql = postgres({
1034+
...options,
1035+
max: 1,
1036+
onidle: () => idleCount++
1037+
})
1038+
1039+
const reserved = await sql.reserve()
1040+
await reserved`select 1`
1041+
reserved.release()
1042+
await sql.end()
1043+
1044+
return [1, idleCount]
1045+
})
1046+
1047+
t('onidle fires after transaction completes', async() => {
1048+
let idleCount = 0
1049+
const sql = postgres({
1050+
...options,
1051+
max: 1,
1052+
onidle: () => idleCount++
1053+
})
1054+
1055+
await sql.begin(async sql => {
1056+
await sql`select 1`
1057+
})
1058+
await sql.end()
1059+
1060+
return [1, idleCount]
1061+
})
1062+
9621063
t('has server parameters', async() => {
9631064
return ['postgres.js', (await sql`select 1`.then(() => sql.parameters.application_name))]
9641065
})

types/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
119119
*/
120120
publications: string
121121
onclose: (connId: number) => void;
122+
/**
123+
* Called when a connection returns to the pool after completing queries
124+
*/
125+
onidle: (connId: number) => void;
122126
backoff: boolean | ((attemptNum: number) => number);
123127
max_lifetime: number | null;
124128
keep_alive: number | null;

0 commit comments

Comments
 (0)