Hone logo
Hone
Problems

Connection Pool Implementation in Python

Connection pools are a crucial optimization technique for database-driven applications. They reduce the overhead of repeatedly establishing and closing database connections, significantly improving performance, especially under high load. This challenge asks you to implement a basic connection pool in Python.

Problem Description

You are tasked with creating a ConnectionPool class that manages a pool of database connections. The pool should support acquiring connections, releasing them back to the pool, and handling cases where all connections are in use. The pool should also have a maximum size to prevent resource exhaustion.

Key Requirements:

  • Initialization: The ConnectionPool should be initialized with a max_size parameter, specifying the maximum number of connections the pool can hold. It should also accept a create_connection function as an argument. This function is responsible for establishing a new database connection (e.g., using psycopg2.connect() for PostgreSQL or sqlite3.connect() for SQLite).
  • Acquire Connection: An acquire() method should retrieve a connection from the pool. If no connections are available, it should block until a connection becomes available (i.e., a connection is released).
  • Release Connection: A release() method should return a connection to the pool, making it available for reuse.
  • Thread Safety: The pool should be thread-safe, allowing multiple threads to acquire and release connections concurrently without data corruption.
  • Connection Validation (Optional but Recommended): Consider adding a mechanism to validate connections before returning them to the user. A stale connection might be unusable.

Expected Behavior:

  • The pool should maintain a limited number of connections, as specified by max_size.
  • acquire() should block if all connections are currently in use.
  • release() should return the connection to the pool for reuse.
  • The pool should handle potential errors during connection acquisition or release gracefully.

Edge Cases to Consider:

  • What happens if create_connection fails? The pool should handle this gracefully (e.g., by logging an error and potentially retrying).
  • What happens if a connection is released multiple times?
  • What happens if a connection is acquired and then lost (e.g., due to a network error)? Ideally, the pool should detect this and replace the connection.
  • What happens if max_size is 0 or negative?

Examples

Example 1:

Input: max_size=2, create_connection=lambda: "Connection 1"
Thread 1: acquire() -> "Connection 1"
Thread 2: acquire() -> "Connection 2"
Thread 3: acquire() -> (blocks)
Thread 1: release("Connection 1")
Thread 3: acquire() -> "Connection 1"

Output: Thread 1: Returns "Connection 1" Thread 2: Returns "Connection 2" Thread 3: Returns "Connection 1"

Explanation: The pool initially has two connections. Thread 1 and 2 acquire them. Thread 3 blocks until Thread 1 releases its connection.

Example 2:

Input: max_size=1, create_connection=lambda: "Connection 1"
Thread 1: acquire() -> "Connection 1"
Thread 2: acquire() -> (blocks)
Thread 1: release("Connection 1")
Thread 2: acquire() -> "Connection 1"

Output: Thread 1: Returns "Connection 1" Thread 2: Returns "Connection 1"

Explanation: With a max size of 1, only one connection is available. Thread 2 blocks until Thread 1 releases it.

Example 3: (Edge Case - Connection Failure)

Input: max_size=1, create_connection=lambda: None  # Simulates connection failure
Thread 1: acquire() -> (blocks indefinitely, or raises an exception depending on implementation)

Output: (Implementation dependent - either blocks indefinitely or raises an exception)

Explanation: If the connection creation function always fails, the pool will never have a valid connection and acquire() will block forever (or raise an exception if your implementation includes a timeout).

Constraints

  • max_size must be a positive integer.
  • The create_connection function must return a valid connection object (or raise an exception if connection creation fails).
  • The implementation must be thread-safe. Consider using threading.Lock or threading.RLock for synchronization.
  • The acquire() method should block for a reasonable amount of time (e.g., 10 seconds) if no connections are available before raising a TimeoutError.
  • The pool should not consume excessive memory.

Notes

  • You can use Python's built-in threading module for thread safety.
  • Consider using a queue to manage the available connections.
  • Think about how to handle connection errors and stale connections.
  • This is a simplified connection pool implementation. Real-world connection pools often include more advanced features, such as connection validation, connection timeouts, and connection pooling statistics.
  • Focus on the core functionality of acquiring, releasing, and managing a limited number of connections. Error handling and advanced features can be added later.
Loading editor...
python