Skip to content

Commit 41a3d9e

Browse files
oshairyanlewis
andauthored
Add minIdleObjects/minIdleConnections support (#267) (#366)
See issue #267 Co-authored-by: Ryan Lewis <ryan@wpyz.org>
1 parent f8fbd79 commit 41a3d9e

File tree

5 files changed

+81
-7
lines changed

5 files changed

+81
-7
lines changed

db-async-common/src/main/java/com/github/jasync/sql/db/ConnectionPoolConfiguration.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import java.util.function.Supplier
2929
* @param maxActiveConnections how many conncetions this pool will keep live
3030
* @param maxIdleTime number of milliseconds for which the objects are going to be kept as idle (not in use by clients of the pool)
3131
* @param maxPendingQueries when there are no more connections, the pool can queue up requests to serve later then there
32-
* are connections available, this is the maximum number of enqueued requests
32+
* are connections available, this is the maximum number of enqueued requests
3333
* @param connectionValidationInterval pools will use this value as the timer period to validate idle objects.
3434
* @param connectionCreateTimeout the timeout for connecting to servers
3535
* @param connectionTestTimeout the timeout for connection tests performed by pools
@@ -51,6 +51,7 @@ import java.util.function.Supplier
5151
* @param currentSchema optional search_path for the database
5252
* @param socketPath path to unix domain socket file (on the local machine)
5353
* @param credentialsProvider a credential provider used to inject credentials on demand
54+
* @param minIdleConnections a minimal number of connections to always keep open (create in advance if needed)
5455
*/
5556
data class ConnectionPoolConfiguration @JvmOverloads constructor(
5657
val host: String = "localhost",
@@ -77,7 +78,8 @@ data class ConnectionPoolConfiguration @JvmOverloads constructor(
7778
val maxConnectionTtl: Long? = null,
7879
val currentSchema: String? = null,
7980
val socketPath: String? = null,
80-
val credentialsProvider: CredentialsProvider? = null
81+
val credentialsProvider: CredentialsProvider? = null,
82+
val minIdleConnections: Int? = null,
8183
) {
8284
init {
8385
require(port > 0) { "port should be positive: $port" }
@@ -90,6 +92,10 @@ data class ConnectionPoolConfiguration @JvmOverloads constructor(
9092
require(connectionTestTimeout >= 0) { "connectionTestTimeout should not be negative: $connectionTestTimeout" }
9193
queryTimeout?.let { require(it >= 0) { "queryTimeout should not be negative: $it" } }
9294
maxConnectionTtl?.let { require(it >= 0) { "queryTimeout should not be negative: $it" } }
95+
minIdleConnections?.let {
96+
require(minIdleConnections >= 0) { "minIdleConnections should not be negative: $it" }
97+
require(minIdleConnections <= maxActiveConnections) { "minIdleConnections should not be bigger than maxActiveConnections: $it > $maxActiveConnections" }
98+
}
9399
}
94100

95101
val connectionConfiguration =
@@ -123,7 +129,8 @@ data class ConnectionPoolConfiguration @JvmOverloads constructor(
123129
createTimeout = connectionCreateTimeout * 2,
124130
testTimeout = connectionTestTimeout,
125131
queryTimeout = queryTimeout,
126-
coroutineDispatcher = coroutineDispatcher
132+
coroutineDispatcher = coroutineDispatcher,
133+
minIdleObjects = minIdleConnections,
127134
)
128135

129136
override fun toString() = """ConnectionPoolConfiguration(host=$host, port=REDACTED,
@@ -143,6 +150,7 @@ data class ConnectionPoolConfiguration @JvmOverloads constructor(
143150
|applicationName=$applicationName,
144151
|interceptors=$interceptors,
145152
|maxConnectionTtl=$maxConnectionTtl
153+
|minIdleConnections=$minIdleConnections)""${'"'}.trimMargin()
146154
|)""".trimMargin()
147155
}
148156

@@ -176,7 +184,8 @@ data class ConnectionPoolConfigurationBuilder @JvmOverloads constructor(
176184
var maxConnectionTtl: Long? = null,
177185
var currentSchema: String? = null,
178186
var socketPath: String? = null,
179-
var credentialsProvider: CredentialsProvider? = null
187+
var credentialsProvider: CredentialsProvider? = null,
188+
var minIdleConnections: Int? = null
180189
) {
181190
fun build(): ConnectionPoolConfiguration = ConnectionPoolConfiguration(
182191
host = host,
@@ -202,6 +211,7 @@ data class ConnectionPoolConfigurationBuilder @JvmOverloads constructor(
202211
interceptors = interceptors,
203212
currentSchema = currentSchema,
204213
socketPath = socketPath,
205-
credentialsProvider = credentialsProvider
214+
credentialsProvider = credentialsProvider,
215+
minIdleConnections = minIdleConnections,
206216
)
207217
}

db-async-common/src/test/java/com/github/jasync/sql/db/ConnectionPoolConfigurationTest.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ class ConnectionPoolConfigurationTest {
2929
queryTimeout = 16,
3030
maximumMessageSize = 17,
3131
applicationName = "applicationName",
32-
maxConnectionTtl = 18
32+
maxConnectionTtl = 18,
33+
minIdleConnections = 5,
3334
).build()
3435
assertThat(configuration.host).isEqualTo("host")
3536
assertThat(configuration.connectionConfiguration.host).isEqualTo("host")
@@ -52,6 +53,7 @@ class ConnectionPoolConfigurationTest {
5253
assertThat(configuration.connectionTestTimeout).isEqualTo(15)
5354
assertThat(configuration.poolConfiguration.testTimeout).isEqualTo(15)
5455
assertThat(configuration.poolConfiguration.queryTimeout).isEqualTo(16)
56+
assertThat(configuration.minIdleConnections).isEqualTo(5)
5557
}
5658

5759
@Test(expected = IllegalArgumentException::class)
@@ -109,4 +111,11 @@ class ConnectionPoolConfigurationTest {
109111
queryTimeout = -1
110112
).build()
111113
}
114+
115+
@Test(expected = IllegalArgumentException::class)
116+
fun `test error minIdleConnections`() {
117+
ConnectionPoolConfigurationBuilder(
118+
minIdleConnections = -1
119+
).build()
120+
}
112121
}

pool-async/src/main/java/com/github/jasync/sql/db/pool/ActorBasedObjectPool.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ private class ObjectPoolActor<T : PooledObject>(
256256
createObject(null)
257257
logger.trace { "scheduleNewItemsIfNeeded - creating new object ; $poolStatusString" }
258258
}
259+
260+
while (configuration.minIdleObjects != null && (availableItems.size + inCreateItems.size) < configuration.minIdleObjects &&
261+
totalItems < configuration.maxObjects
262+
) {
263+
createObject(null)
264+
logger.trace { "scheduleNewItemsIfNeeded - creating new object to meet minIdleObjects=${configuration.minIdleObjects} ; $poolStatusString" }
265+
}
259266
}
260267

261268
private val poolStatusString: String

pool-async/src/main/java/com/github/jasync/sql/db/pool/PoolConfiguration.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import kotlinx.coroutines.Dispatchers
1717
* @param testTimeout the timeout for connection tests performed by pools
1818
* @param queryTimeout the optional query timeout
1919
* @param coroutineDispatcher thread pool for the actor operations of the connection pool
20+
* @param minIdleObjects the minimum number of objects this pool should hold
2021
*/
2122

2223
data class PoolConfiguration @JvmOverloads constructor(
@@ -28,7 +29,8 @@ data class PoolConfiguration @JvmOverloads constructor(
2829
val testTimeout: Long = 5000,
2930
val queryTimeout: Long? = null,
3031
val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default,
31-
val maxObjectTtl: Long? = null
32+
val maxObjectTtl: Long? = null,
33+
val minIdleObjects: Int? = null
3234
) {
3335
companion object {
3436
@Suppress("unused")

pool-async/src/test/java/com/github/jasync/sql/db/pool/ActorBasedObjectPoolTest.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,52 @@ class ActorBasedObjectPoolTest {
317317
System.gc() // to show leak in logging
318318
Thread.sleep(1000)
319319
}
320+
321+
@Test
322+
fun `test minIdleObjects - we maintain a minimum number of objects`() {
323+
tested = ActorBasedObjectPool(
324+
factory,
325+
configuration.copy(minIdleObjects = 3),
326+
false
327+
)
328+
tested.take().get()
329+
Thread.sleep(20)
330+
assertThat(tested.availableItemsSize).isEqualTo(3)
331+
}
332+
333+
@Test
334+
fun `test minIdleObjects - when min = max, we don't go over the total number when returning back`() {
335+
tested = ActorBasedObjectPool(
336+
factory,
337+
configuration.copy(maxObjects = 3, minIdleObjects = 3),
338+
false
339+
)
340+
val widget = tested.take().get()
341+
Thread.sleep(20)
342+
// 3 max, one active, meaning expecting 2 available
343+
assertThat(tested.availableItemsSize).isEqualTo(2)
344+
tested.giveBack(widget).get()
345+
Thread.sleep(20)
346+
assertThat(tested.availableItemsSize).isEqualTo(3)
347+
}
348+
349+
@Test
350+
fun `test minIdleObjects - cleaned up objects result in more objects being created`() {
351+
tested = ActorBasedObjectPool(
352+
factory,
353+
configuration.copy(maxObjects = 3, minIdleObjects = 3, maxObjectTtl = 50),
354+
false
355+
)
356+
val widget = tested.take().get()
357+
tested.giveBack(widget).get()
358+
Thread.sleep(20)
359+
assertThat(tested.availableItemsSize).isEqualTo(3)
360+
assertThat(factory.created.size).isEqualTo(3)
361+
Thread.sleep(70)
362+
tested.testAvailableItems()
363+
await.untilCallTo { tested.availableItemsSize } matches { it == 3 }
364+
await.untilCallTo { factory.created.size } matches { it == 6 }
365+
}
320366
}
321367

322368
private var widgetId = 0

0 commit comments

Comments
 (0)