0
# Connection Pooling
1
2
Connection pool management for handling multiple concurrent LDAP connections with automatic lifecycle management, configurable pool sizes, and thread-safe operations.
3
4
## Capabilities
5
6
### Connection Pool Management
7
8
Generic connection pool for managing multiple LDAP connections with configurable minimum and maximum connections.
9
10
```python { .api }
11
from bonsai.pool import ConnectionPool, PoolError, ClosedPool, EmptyPool
12
13
class ConnectionPool:
14
def __init__(
15
self,
16
client: LDAPClient,
17
minconn: int = 1,
18
maxconn: int = 10,
19
**kwargs
20
) -> None:
21
"""
22
Initialize connection pool.
23
24
Parameters:
25
- client: LDAPClient for creating connections
26
- minconn: Minimum connections to maintain (default: 1)
27
- maxconn: Maximum connections allowed (default: 10)
28
- **kwargs: Additional arguments passed to LDAPClient.connect()
29
"""
30
31
def open(self) -> None:
32
"""
33
Open connection pool and create minimum number of connections.
34
35
Creates minconn connections and marks pool as open for use.
36
"""
37
38
def close(self) -> None:
39
"""
40
Close connection pool and all connections.
41
42
Closes all idle and used connections, marks pool as closed.
43
"""
44
45
def get(self) -> LDAPConnection:
46
"""
47
Get connection from pool.
48
49
Returns:
50
Available LDAP connection from pool
51
52
Raises:
53
- ClosedPool: If pool is closed
54
- EmptyPool: If no connections available and maxconn reached
55
"""
56
57
def put(self, conn: LDAPConnection) -> None:
58
"""
59
Return connection to pool.
60
61
Parameters:
62
- conn: LDAP connection to return to pool
63
64
Note: Connection must have been obtained from this pool
65
"""
66
67
def spawn(self, minconn: Optional[int] = None) -> LDAPConnection:
68
"""
69
Create new connection and add to pool.
70
71
Parameters:
72
- minconn: Minimum connections to maintain after spawn
73
74
Returns:
75
New LDAP connection from pool
76
"""
77
78
@property
79
def closed(self) -> bool:
80
"""Whether the pool is closed."""
81
82
@property
83
def idle_connection(self) -> int:
84
"""Number of idle connections in pool."""
85
86
@property
87
def shared_connection(self) -> int:
88
"""Number of connections currently in use."""
89
90
@property
91
def max_connection(self) -> int:
92
"""Maximum number of connections allowed."""
93
94
@property
95
def min_connection(self) -> int:
96
"""Minimum number of connections to maintain."""
97
98
def __enter__(self) -> "ConnectionPool":
99
"""Context manager entry - opens pool."""
100
101
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
102
"""Context manager exit - closes pool."""
103
```
104
105
### Pool Context Manager
106
107
Context manager for automatically managing connection lifecycle within a pool.
108
109
```python { .api }
110
@contextmanager
111
def PooledConnection(pool: ConnectionPool) -> Generator[LDAPConnection, None, None]:
112
"""
113
Context manager for getting and returning pooled connections.
114
115
Parameters:
116
- pool: ConnectionPool to get connection from
117
118
Yields:
119
LDAP connection from pool
120
121
Usage:
122
with PooledConnection(pool) as conn:
123
# Use connection
124
results = conn.search(...)
125
# Connection automatically returned to pool
126
"""
127
```
128
129
### Pool Exceptions
130
131
Specialized exceptions for connection pool error handling.
132
133
```python { .api }
134
class PoolError(Exception):
135
"""Base exception for connection pool related errors."""
136
137
class ClosedPool(PoolError):
138
"""Raised when attempting operations on a closed pool."""
139
140
class EmptyPool(PoolError):
141
"""Raised when pool has no available connections and cannot create more."""
142
```
143
144
### AsyncIO Connection Pool
145
146
Specialized connection pool for asyncio environments with async connection management.
147
148
```python { .api }
149
from bonsai.asyncio import AIOConnectionPool
150
151
class AIOConnectionPool:
152
def __init__(
153
self,
154
client: LDAPClient,
155
minconn: int = 1,
156
maxconn: int = 10,
157
loop=None,
158
**kwargs
159
) -> None:
160
"""
161
Initialize asyncio connection pool.
162
163
Parameters:
164
- client: LDAPClient for creating connections
165
- minconn: Minimum connections to maintain
166
- maxconn: Maximum connections allowed
167
- loop: asyncio event loop
168
- **kwargs: Additional connection arguments
169
"""
170
171
async def open(self) -> None:
172
"""Async open connection pool."""
173
174
async def close(self) -> None:
175
"""Async close connection pool and all connections."""
176
177
async def get(self, timeout: Optional[float] = None) -> AIOLDAPConnection:
178
"""
179
Async get connection from pool.
180
181
Parameters:
182
- timeout: Maximum time to wait for connection
183
184
Returns:
185
AsyncIO LDAP connection
186
"""
187
188
async def put(self, conn: AIOLDAPConnection) -> None:
189
"""
190
Async return connection to pool.
191
192
Parameters:
193
- conn: AsyncIO LDAP connection to return
194
"""
195
196
async def __aenter__(self) -> "AIOConnectionPool":
197
"""Async context manager entry."""
198
199
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
200
"""Async context manager exit."""
201
202
@property
203
def closed(self) -> bool:
204
"""Whether the pool is closed."""
205
206
@property
207
def idle_connection(self) -> int:
208
"""Number of idle connections."""
209
210
@property
211
def shared_connection(self) -> int:
212
"""Number of connections in use."""
213
```
214
215
### Threaded Connection Pool
216
217
Thread-safe connection pool for multi-threaded applications with blocking behavior control.
218
219
```python { .api }
220
from bonsai.pool import ThreadedConnectionPool
221
222
class ThreadedConnectionPool(ConnectionPool[LDAPConnection]):
223
def __init__(
224
self,
225
client: LDAPClient,
226
minconn: int = 1,
227
maxconn: int = 10,
228
block: bool = True,
229
**kwargs
230
) -> None:
231
"""
232
Initialize thread-safe connection pool.
233
234
Parameters:
235
- client: LDAPClient for creating connections
236
- minconn: Minimum connections to maintain
237
- maxconn: Maximum connections allowed
238
- block: Whether to block when pool is empty (True) or raise EmptyPool (False)
239
- **kwargs: Additional connection arguments
240
"""
241
242
def get(self, timeout: Optional[float] = None) -> LDAPConnection:
243
"""
244
Thread-safe get connection from pool.
245
246
Parameters:
247
- timeout: Maximum time to wait for connection (only if block=True)
248
249
Returns:
250
LDAP connection from pool
251
252
Raises:
253
- EmptyPool: If no connections available and block=False
254
- TimeoutError: If timeout exceeded while waiting
255
"""
256
257
def put(self, conn: LDAPConnection) -> None:
258
"""
259
Thread-safe return connection to pool.
260
261
Parameters:
262
- conn: LDAP connection to return
263
"""
264
265
@property
266
def block(self) -> bool:
267
"""Whether get() blocks when pool is empty."""
268
```
269
270
## Usage Examples
271
272
### Basic Connection Pool Usage
273
274
```python
275
from bonsai import LDAPClient
276
from bonsai.pool import ConnectionPool
277
278
# Configure client
279
client = LDAPClient("ldap://localhost")
280
client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")
281
282
# Create connection pool
283
pool = ConnectionPool(client, minconn=2, maxconn=10)
284
285
# Use pool with context manager
286
with pool:
287
# Get connection from pool
288
conn = pool.get()
289
290
try:
291
# Perform LDAP operations
292
results = conn.search("dc=example,dc=com", 2, "(objectClass=person)")
293
print(f"Found {len(results)} entries")
294
295
# Add new entry
296
from bonsai import LDAPEntry
297
new_entry = LDAPEntry("cn=pooled-user,dc=example,dc=com")
298
new_entry['objectClass'] = ['person']
299
new_entry['cn'] = 'pooled-user'
300
new_entry['sn'] = 'User'
301
conn.add(new_entry)
302
303
finally:
304
# Return connection to pool
305
pool.put(conn)
306
307
# Pool automatically closes when exiting context
308
```
309
310
### Thread-Safe Pool Operations
311
312
```python
313
import threading
314
from bonsai import LDAPClient
315
from bonsai.pool import ConnectionPool
316
317
client = LDAPClient("ldap://localhost")
318
client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")
319
320
# Create shared pool for multiple threads
321
pool = ConnectionPool(client, minconn=5, maxconn=20)
322
pool.open()
323
324
def worker_thread(thread_id, pool):
325
"""Worker function for thread-safe LDAP operations."""
326
try:
327
# Get connection from pool (thread-safe)
328
conn = pool.get()
329
330
try:
331
# Perform operations unique to this thread
332
filter_exp = f"(description=thread-{thread_id})"
333
results = conn.search("dc=example,dc=com", 2, filter_exp)
334
print(f"Thread {thread_id}: Found {len(results)} entries")
335
336
# Each thread can perform independent operations
337
if not results:
338
# Create test entry for this thread
339
entry = LDAPEntry(f"cn=thread-{thread_id},dc=example,dc=com")
340
entry['objectClass'] = ['person', 'organizationalPerson']
341
entry['cn'] = f'thread-{thread_id}'
342
entry['sn'] = f'User{thread_id}'
343
entry['description'] = f'thread-{thread_id}'
344
conn.add(entry)
345
print(f"Thread {thread_id}: Created test entry")
346
347
finally:
348
# Always return connection to pool
349
pool.put(conn)
350
351
except Exception as e:
352
print(f"Thread {thread_id} error: {e}")
353
354
# Start multiple worker threads
355
threads = []
356
for i in range(10):
357
thread = threading.Thread(target=worker_thread, args=(i, pool))
358
threads.append(thread)
359
thread.start()
360
361
# Wait for all threads to complete
362
for thread in threads:
363
thread.join()
364
365
# Clean up
366
pool.close()
367
print("All threads completed, pool closed")
368
```
369
370
### Pooled Connection Context Manager
371
372
```python
373
from bonsai.pool import ConnectionPool, PooledConnection
374
375
client = LDAPClient("ldap://localhost")
376
client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")
377
378
# Create and open pool
379
pool = ConnectionPool(client, minconn=3, maxconn=15)
380
pool.open()
381
382
try:
383
# Use pooled connection context manager
384
with PooledConnection(pool) as conn:
385
# Connection automatically obtained from pool
386
results = conn.search("dc=example,dc=com", 2, "(objectClass=organizationalUnit)")
387
388
for entry in results:
389
print(f"OU: {entry.dn}")
390
391
# Create new organizational unit
392
new_ou = LDAPEntry("ou=departments,dc=example,dc=com")
393
new_ou['objectClass'] = ['organizationalUnit']
394
new_ou['ou'] = 'departments'
395
new_ou['description'] = 'Department organizational units'
396
conn.add(new_ou)
397
398
# Connection automatically returned to pool when exiting context
399
400
# Use another pooled connection
401
with PooledConnection(pool) as conn:
402
# This might reuse the previous connection or get a different one
403
results = conn.search("ou=departments,dc=example,dc=com", 1)
404
print(f"Found {len(results)} departments")
405
406
finally:
407
pool.close()
408
```
409
410
### AsyncIO Pool Usage
411
412
```python
413
import asyncio
414
from bonsai import LDAPClient
415
from bonsai.asyncio import AIOConnectionPool
416
417
async def async_pool_operations():
418
client = LDAPClient("ldap://localhost")
419
client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")
420
421
# Create async connection pool
422
async with AIOConnectionPool(client, minconn=2, maxconn=8) as pool:
423
# Get connection from pool
424
conn = await pool.get(timeout=5.0)
425
426
try:
427
# Async LDAP operations
428
results = await conn.search("dc=example,dc=com", 2, "(objectClass=person)")
429
print(f"Found {len(results)} person entries")
430
431
# Concurrent operations using multiple connections
432
tasks = []
433
for i in range(3):
434
task = asyncio.create_task(search_with_pool(pool, f"(cn=user{i})"))
435
tasks.append(task)
436
437
# Wait for all searches to complete
438
search_results = await asyncio.gather(*tasks)
439
for i, results in enumerate(search_results):
440
print(f"Search {i}: {len(results)} results")
441
442
finally:
443
# Return connection to pool
444
await pool.put(conn)
445
446
async def search_with_pool(pool, filter_exp):
447
"""Helper function for concurrent searches."""
448
conn = await pool.get()
449
try:
450
results = await conn.search("dc=example,dc=com", 2, filter_exp)
451
return results
452
finally:
453
await pool.put(conn)
454
455
# Run async operations
456
asyncio.run(async_pool_operations())
457
```
458
459
### Pool Monitoring and Management
460
461
```python
462
from bonsai import LDAPClient
463
from bonsai.pool import ConnectionPool, EmptyPool, ClosedPool
464
import time
465
466
client = LDAPClient("ldap://localhost")
467
client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")
468
469
# Create pool with specific sizing
470
pool = ConnectionPool(client, minconn=2, maxconn=5)
471
pool.open()
472
473
print(f"Pool opened with {pool.min_connection} min, {pool.max_connection} max connections")
474
print(f"Initial state: {pool.idle_connection} idle, {pool.shared_connection} in use")
475
476
# Get multiple connections to test limits
477
connections = []
478
try:
479
for i in range(7): # Try to get more than maxconn
480
try:
481
conn = pool.get()
482
connections.append(conn)
483
print(f"Got connection {i+1}: {pool.idle_connection} idle, {pool.shared_connection} in use")
484
485
except EmptyPool:
486
print(f"Pool empty after {i} connections - hit maximum limit")
487
break
488
489
# Return some connections
490
for i in range(2):
491
if connections:
492
pool.put(connections.pop())
493
print(f"Returned connection: {pool.idle_connection} idle, {pool.shared_connection} in use")
494
495
# Try operations on closed pool
496
pool.close()
497
print("Pool closed")
498
499
try:
500
pool.get()
501
except ClosedPool:
502
print("Cannot get connection from closed pool")
503
504
finally:
505
# Clean up any remaining connections
506
for conn in connections:
507
try:
508
conn.close()
509
except:
510
pass
511
```