or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-directory.mdasync-frameworks.mdconnection-pooling.mdcore-ldap.mdindex.mdldif-support.mdutilities-errors.md

connection-pooling.mddocs/

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

```