Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection pool #177

Merged
merged 26 commits into from
Oct 16, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
263d9a3
Add pool implementation for SQLite.
moigagoo Oct 15, 2022
087c324
Add tests for connection pool.
moigagoo Oct 15, 2022
9c71710
Add new test to gitignore.
moigagoo Oct 15, 2022
20a6ef2
Enable --mm:orc for tests.
moigagoo Oct 15, 2022
2eb62a3
Minor formatting cleanup.
moigagoo Oct 15, 2022
6cdf12f
Add doc comments.
moigagoo Oct 15, 2022
6847824
Define --mm:orc for the necessary test only.
moigagoo Oct 15, 2022
ff252e4
Fix doc comments, add pool to API docs.
moigagoo Oct 15, 2022
41bdd85
Minor formatting fixes.
moigagoo Oct 15, 2022
ba02980
Minor formatting fixes.
moigagoo Oct 15, 2022
e94fb5b
Remove redundant check.
moigagoo Oct 15, 2022
20dee37
Update pool implenetation to support postgres.
moigagoo Oct 15, 2022
1e152cb
SQLite: Update pool tests to support the new implementation.
moigagoo Oct 15, 2022
f106d68
Postgres: Add tests for pool.
moigagoo Oct 15, 2022
45d15ae
Add blank line.
moigagoo Oct 15, 2022
d144707
Add blank lines.
moigagoo Oct 15, 2022
b3a5217
Book: Add chapter on connection pooling.
moigagoo Oct 15, 2022
bf3e13b
Book: Improve chapter on pooling.
moigagoo Oct 15, 2022
7cf3271
Update changelog.
moigagoo Oct 15, 2022
60a2b22
Rename Conn to DbConn for consistency.
moigagoo Oct 16, 2022
c4798be
rowutils: Fix docstrings.
moigagoo Oct 16, 2022
d372fa9
Book: Pool: Add code samples and prose on closing the pool.
moigagoo Oct 16, 2022
bec892f
Changelog: Set next version to 2.6.0
moigagoo Oct 16, 2022
912e948
Fix book task.
moigagoo Oct 16, 2022
70f93f9
Allow arbitrary getDb function to be passed to newPool.
moigagoo Oct 16, 2022
75c9610
Add tests for pooling with a custom connection provider.
moigagoo Oct 16, 2022
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tests/sqlite/tfkpragma_selfref
tests/sqlite/tfkpragmanotint
tests/sqlite/tfkpragmanotmodel
tests/sqlite/tnull
tests/sqlite/tpool
tests/sqlite/trelated
tests/sqlite/trepeatfks
tests/sqlite/trawselect
Expand Down
69 changes: 69 additions & 0 deletions src/norm/pool.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import std/locks

import sqlite


type
Pool* = ref object
defaultSize: Natural
conns: seq[DbConn]
poolExhaustedPolicy: PoolExhaustedPolicy
lock: Lock
PoolExhaustedError* = object of CatchableError
PoolExhaustedPolicy* = enum
pepRaise
pepExtend


func newPool*(defaultSize: Positive, poolExhaustedPolicy = pepRaise): Pool =
moigagoo marked this conversation as resolved.
Show resolved Hide resolved
result = Pool(defaultSize: defaultSize, conns: newSeq[DbConn](defaultSize), poolExhaustedPolicy: poolExhaustedPolicy)

initLock(result.lock)

for conn in result.conns.mitems:
conn = getDb()

func defaultSize*(pool: Pool): Natural =
pool.defaultSize

func size*(pool: Pool): Natural =
len(pool.conns)

func pop*(pool: var Pool): DbConn =
withLock(pool.lock):
if pool.size > 0:
result = pool.conns.pop()
else:
case pool.poolExhaustedPolicy
of pepRaise:
raise newException(PoolExhaustedError, "Pool exhausted")
of pepExtend:
result = getDb()

func add*(pool: var Pool, dbConn: DbConn) =
withLock(pool.lock):
pool.conns.add(dbConn)

func reset*(pool: var Pool) =
withLock(pool.lock):
while pool.size > pool.defaultSize:
var conn = pool.conns.pop()
close conn

func close*(pool: var Pool) =
withLock(pool.lock):
for conn in pool.conns.mitems:
close conn

pool.conns.setLen(0)

template withDb*(pool: var Pool, body: untyped): untyped =
moigagoo marked this conversation as resolved.
Show resolved Hide resolved
block:
let db {.inject.} = pool.pop()

try:
body

finally:
pool.add(db)

1 change: 1 addition & 0 deletions tests/config.nims
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
switch("path", "$projectDir/../src")
switch("define", "normDebug")
switch("deepcopy", "on")
switch("mm", "orc")
moigagoo marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions tests/sqlite/tenv.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ suite "DB config from environment variables":
@[?0, ?"price", ?"FLOAT", ?1, ?nil, ?0],
@[?1, ?"id", ?"INTEGER", ?1, ?nil, ?1],
]

161 changes: 161 additions & 0 deletions tests/sqlite/tpool.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import std/[unittest, os, strutils]

import norm/[sqlite, pool]

import ../models

const dbFile = "test.db"

suite "Connection pool":
setup:
removeFile dbFile
putEnv(dbHostEnv, dbFile)

teardown:
delEnv(dbHostEnv)
removeFile dbFile

test "Create and close pool":
var pool = newPool(1)

check pool.defaultSize == 1
check pool.size == 1

close pool

check pool.size == 0

test "Explicit pool connection":
var pool = newPool(1)
let db = pool.pop()

db.createTables(newToy())

let qry = "PRAGMA table_info($#);"

check db.getAllRows(sql qry % "Toy") == @[
@[?0, ?"price", ?"FLOAT", ?1, ?nil, ?0],
@[?1, ?"id", ?"INTEGER", ?1, ?nil, ?1],
]

pool.add(db)
close pool

test "Implicit pool connection":
var pool = newPool(1)

withDb(pool):
db.createTables(newToy())

let qry = "PRAGMA table_info($#);"

check db.getAllRows(sql qry % "Toy") == @[
@[?0, ?"price", ?"FLOAT", ?1, ?nil, ?0],
@[?1, ?"id", ?"INTEGER", ?1, ?nil, ?1],
]

close pool

test "Concurrent pool connections":
var
pool = newPool(2)
toy1 = newToy(123.45)
toy2 = newToy(456.78)
threads: array[2, Thread[float]]
sum: float

withDb(pool):
db.createTables(toy1)
db.insert(toy1)
db.insert(toy2)

proc getToy(price: float) {.thread.} =
{.cast(gcsafe).}:
var toy = newToy()

withDb(pool):
db.select(toy, "price = ?", price)

sum += toy.price

createThread(threads[0], getToy, 123.45)
createThread(threads[1], getToy, 456.78)

joinThreads(threads)

check sum == toy1.price + toy2.price

close pool

test "Pool exhausted, raise exception":
var
pool = newPool(1, pepRaise)
toy1 = newToy(123.45)
toy2 = newToy(456.78)
threads: array[2, Thread[float]]
exceptionRaised: bool

withDb(pool):
db.createTables(toy1)
db.insert(toy1)
db.insert(toy2)

proc getToy(price: float) {.thread.} =
{.cast(gcsafe).}:
var toy = newToy()

try:
withDb(pool):
db.select(toy, "price = ?", price)
while not exceptionRaised:
sleep 100
except PoolExhaustedError:
exceptionRaised = true

createThread(threads[0], getToy, 123.45)
createThread(threads[1], getToy, 456.78)

joinThreads(threads)

check exceptionRaised

close pool

test "Pool exhausted, extend and reset pool":
var
pool = newPool(1, pepExtend)
toy1 = newToy(123.45)
toy2 = newToy(456.78)
threads: array[2, Thread[float]]
maxActiveConnectionCount: Natural

withDb(pool):
db.createTables(toy1)
db.insert(toy1)
db.insert(toy2)

proc getToy(price: float) {.thread.} =
{.cast(gcsafe).}:
var toy = newToy()

withDb(pool):
inc maxActiveConnectionCount

db.select(toy, "price = ?", price)

while maxActiveConnectionCount < 2:
sleep 100

createThread(threads[0], getToy, 123.45)
createThread(threads[1], getToy, 456.78)

joinThreads(threads)

check maxActiveConnectionCount == 2

reset pool

check pool.size == pool.defaultSize

close pool

2 changes: 2 additions & 0 deletions tests/sqlite/tpool.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
switch("threads", "on")

14 changes: 7 additions & 7 deletions tests/sqlite/trows.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ suite "Row CRUD":
removeFile dbFile

test "Insert row":
var toy = newtoy(123.45)
var toy = newToy(123.45)

dbConn.insert(toy)

Expand All @@ -31,7 +31,7 @@ suite "Row CRUD":
check rows[0] == @[?123.45, ?toy.id]

test "Insert row twice":
var toy = newtoy(123.45)
var toy = newToy(123.45)

dbConn.insert(toy)
dbConn.insert(toy, force = true, conflictPolicy = cpReplace)
Expand All @@ -44,31 +44,31 @@ suite "Row CRUD":
check rows[^1] == @[?123.45, ?toy.id]

test "Insert with forced id":
var toy = newtoy(137.45)
var toy = newToy(137.45)
toy.id = 134
dbConn.insert(toy, force = true)
check toy.id == 134

test "Insert row with forced id in non-incremental order":
block:
var toy = newtoy(123.45)
var toy = newToy(123.45)
toy.id = 3
dbConn.insert(toy, force=true)
check toy.id == 3
block:
var toy = newtoy(123.45)
var toy = newToy(123.45)
toy.id = 2
dbConn.insert(toy, force=true)
check toy.id == 2
block:
# Check no id conflict
var toy = newtoy(123.45)
var toy = newToy(123.45)
dbConn.insert(toy)
# SQLite ids starts from the highest ?
check toy.id == 4
block:
# Check no id conflict
var toy = newtoy(123.45)
var toy = newToy(123.45)
dbConn.insert(toy)
# SQLite ids starts from the highest ?
check toy.id == 5
Expand Down