Project 3: Database Connection Pool
Build a connection pool that enforces lifecycle states and safe resource reuse.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 1-2 weeks |
| Language | C |
| Prerequisites | Threads/locks basics, sockets or DB client API |
| Key Topics | Resource lifecycle, state transitions, contention |
1. Learning Objectives
By completing this project, you will:
- Model connection lifecycle states explicitly.
- Implement acquisition and release with timeouts.
- Prevent invalid state transitions (double release, use-after-close).
- Handle failures while keeping pool consistent.
2. Theoretical Foundation
2.1 Core Concepts
- Resource lifecycle: Idle -> InUse -> Closing -> Closed.
- Pool invariants: Total connections <= max, no duplicates, no leaks.
- Contention: Multiple threads compete for limited resources.
2.2 Why This Matters
Connection pools are a classic example of state-driven resource management. Bugs cause leaks, deadlocks, and outages.
2.3 Historical Context / Background
Early database systems embedded pooling in middleware; now most high-performance services depend on well-behaved pools to stay within limits.
2.4 Common Misconceptions
- “A pool is just a queue”: Lifecycle and failure handling make it a state machine.
- “Closed means gone”: The pool must recycle or destroy safely.
3. Project Specification
3.1 What You Will Build
A thread-safe connection pool with fixed maximum size, timeout-based acquisition, and safe release/cleanup semantics.
3.2 Functional Requirements
- Acquire: Get a connection or wait with timeout.
- Release: Return a connection to the pool.
- Health checks: Validate idle connections before reuse.
- Shutdown: Cleanly close all connections.
3.3 Non-Functional Requirements
- Reliability: No double-free or leaked connections.
- Concurrency: Correct under multiple threads.
- Latency: Acquire should be bounded by timeout.
3.4 Example Usage / Output
pool_init(max=10)
conn = pool_acquire(timeout=1000ms)
pool_release(conn)
3.5 Real World Outcome
Threads can safely share a pool without leaking or over-allocating connections:
[pool] acquire -> conn #3
[pool] release -> conn #3
[pool] shutdown -> closed 10 connections
4. Solution Architecture
4.1 High-Level Design
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ threads │────▶│ pool manager │────▶│ connections │
└──────────────┘ └──────────────┘ └──────────────┘
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Pool struct | Track connections and state | Fixed array + mutex |
| Connection state | Idle/InUse/Closed | Enum with invariants |
| Wait queue | Block on acquire | Condition variable |
4.3 Data Structures
typedef enum {
CONN_IDLE,
CONN_IN_USE,
CONN_CLOSING,
CONN_CLOSED
} ConnState;
typedef struct {
ConnState state;
int fd; /* or DB handle */
} PoolConn;
4.4 Algorithm Overview
Key Algorithm: Acquire/Release
- Lock pool.
- Find idle connection or create new.
- If none, wait with timeout.
- Mark IN_USE and return.
Complexity Analysis:
- Time: O(N) for scanning pool.
- Space: O(N) for connections.
5. Implementation Guide
5.1 Development Environment Setup
gcc --version
5.2 Project Structure
project-root/
├── src/
│ ├── pool.c
│ ├── pool.h
│ └── main.c
└── Makefile
5.3 The Core Question You’re Answering
“What state is each connection in, and what operations are valid right now?”
5.4 Concepts You Must Understand First
Stop and research these before coding:
- Mutex + condition variables
- Correct wait/notify usage
- Resource lifecycle
- Acquire/use/release patterns
- Timeout handling
- Avoid blocking forever
5.5 Questions to Guide Your Design
- How will you prevent two threads from using the same connection?
- What happens if a connection dies while idle?
- How do you enforce the max size?
5.6 Thinking Exercise
If a thread forgets to release a connection, how can your pool detect and mitigate it?
5.7 The Interview Questions They’ll Ask
- Why is a connection pool a state machine?
- How do you handle timeouts in acquisition?
- What invariants must always hold in a pool?
5.8 Hints in Layers
Hint 1: Start single-threaded
- Validate state transitions first.
Hint 2: Add locking
- Protect pool with mutex.
Hint 3: Add condition variable
- Wait when no connections free.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Concurrency | “Operating Systems: Three Easy Pieces” | Threads |
| Resource mgmt | “APUE” | File descriptors and lifecycle |
5.10 Implementation Phases
Phase 1: Foundation (3-4 days)
Goals:
- Implement basic pool with states.
Tasks:
- Add enum states.
- Acquire/release without threads.
Checkpoint: Correct transitions and invariants.
Phase 2: Core Functionality (4-5 days)
Goals:
- Add concurrency.
Tasks:
- Add mutex/condvar.
- Implement timeouts.
Checkpoint: Multiple threads acquire safely.
Phase 3: Polish & Edge Cases (2-3 days)
Goals:
- Failure handling and cleanup.
Tasks:
- Add health checks.
- Add shutdown logic.
Checkpoint: Clean shutdown with no leaks.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Pool size | fixed vs dynamic | fixed | Simpler invariants |
| Idle scan | linear vs queue | linear first | Simplicity |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| State tests | Valid transitions | Idle->InUse->Idle |
| Concurrency | No double use | Two threads acquire |
| Timeout | Bounded waits | Acquire returns timeout |
6.2 Critical Test Cases
- Acquire/release preserves pool count.
- Two threads cannot get same connection.
- Timeout returns error after limit.
6.3 Test Data
max=2, threads=4
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Missing lock | Double use | Lock all state changes |
| Missed signal | Thread hangs | Use while loop on condvar |
| Leak on error | Pool grows | Ensure cleanup path |
7.2 Debugging Strategies
- Add logging around state changes.
- Use assertions for invariants.
7.3 Performance Traps
Linear scans under high concurrency can be slow; optimize later.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add metrics for pool usage.
8.2 Intermediate Extensions
- Add per-connection timeouts and recycling.
8.3 Advanced Extensions
- Implement dynamic resize with safe transitions.
9. Real-World Connections
9.1 Industry Applications
- Databases: Connection pools are core infrastructure.
- Network clients: Reuse of sockets and TLS sessions.
9.2 Related Open Source Projects
- pgbouncer: PostgreSQL pooling service.
- HikariCP: JVM pool with strict invariants.
9.3 Interview Relevance
- Demonstrates state management under concurrency.
10. Resources
10.1 Essential Reading
- APUE - resource lifecycle patterns.
- OSTEP - concurrency and locks.
10.2 Video Resources
- Search: “connection pool design”.
10.3 Tools & Documentation
- pthread:
man pthreads.
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain pool invariants.
- I can describe valid transitions.
11.2 Implementation
- Pool works under concurrency.
- No leaks on shutdown.
11.3 Growth
- I can apply lifecycle logic to other resources.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Thread-safe acquire/release with timeouts.
Full Completion:
- Health checks and clean shutdown.
Excellence (Going Above & Beyond):
- Dynamic resizing with strong invariants.
This guide was generated from SPRINT_3_CONTROL_FLOW_STATE_PROJECTS.md. For the complete learning path, see the parent directory README.