Robust Connection Pool in Go
This challenge focuses on building a connection pool for database connections (or any resource requiring connection management) in Go. Efficient connection management is crucial for applications dealing with limited resources or high concurrency, preventing connection exhaustion and improving performance. You'll design and implement a pool that handles connection acquisition, release, and potentially connection health checks.
Problem Description
You are tasked with creating a ConnectionPool struct in Go that manages a pool of reusable connections. The pool should support the following functionalities:
- Initialization: The pool should be initialized with a maximum number of connections (
maxConnections), an initial number of connections (initialConnections), and a function to create a new connection (connectionFactory). TheconnectionFactoryis a function that takes no arguments and returns aConnectioninterface. - Acquire Connection: A method
Acquire()should block until a connection is available and then return aConnectioninterface. - Release Connection: A method
Release(conn Connection)should return a connection to the pool, making it available for reuse. - Close: A method
Close()should gracefully shut down the pool, closing all connections and preventing new connections from being acquired. - Connection Interface: Define a
Connectioninterface with aClose()method. This allows the pool to be generic and work with different connection types.
Key Requirements:
- Concurrency Safety: The pool must be thread-safe, allowing multiple goroutines to acquire and release connections concurrently without data races. Use appropriate synchronization primitives (e.g., mutexes, channels).
- Blocking Acquisition:
Acquire()should block if all connections are currently in use. - Graceful Shutdown:
Close()should close all connections in the pool and prevent further acquisitions. - Connection Factory: The pool should use a provided
connectionFactoryfunction to create new connections. This allows for flexibility in connection creation.
Expected Behavior:
- The pool should initially create
initialConnectionsconnections. Acquire()should return a connection immediately if one is available. If not, it should block until a connection is released.Release()should return the connection to the pool for reuse.Close()should close all connections and prevent further acquisitions.
Edge Cases to Consider:
- What happens if
maxConnectionsis 0? - What happens if
initialConnectionsis negative? (Handle gracefully, perhaps by setting it to 0). - What happens if the
connectionFactoryfunction fails to create a connection? (Handle the error appropriately, potentially logging it and retrying). - What happens if a connection is released multiple times? (Prevent this, potentially by tracking connection usage).
- What happens if
Close()is called multiple times? (Handle gracefully, preventing panics).
Examples
Example 1:
Input: maxConnections=2, initialConnections=1, connectionFactory=func() Connection { return &MockConnection{id: 1} }
Output: (After acquiring and releasing connections multiple times) The pool maintains a pool of connections, allowing concurrent access without exhausting resources.
Explanation: The pool starts with one connection. Multiple goroutines can acquire and release connections, and the pool ensures that the number of active connections never exceeds 2.
Example 2:
Input: maxConnections=1, initialConnections=0, connectionFactory=func() Connection { return &MockConnection{id: 1} }
Output: (After calling Acquire) A goroutine blocks until a connection is available. Once released, the goroutine receives the connection.
Explanation: The pool starts with no connections. The first `Acquire()` call blocks. When a connection is created and released, the blocking goroutine receives it.
Example 3: (Edge Case)
Input: maxConnections=0, initialConnections=1, connectionFactory=func() Connection { return &MockConnection{id: 1} }
Output: The pool is initialized with one connection, but no connections can be acquired. Close() will close the initial connection.
Explanation: The pool is initialized, but the maximum connection limit is zero, effectively disabling connection acquisition.
Constraints
maxConnectionsmust be a non-negative integer.initialConnectionsmust be a non-negative integer.- The
connectionFactoryfunction must return a value that implements theConnectioninterface. - The pool must be able to handle at least 10 concurrent
Acquire()calls without deadlocking. - The
Close()method should complete within 1 second.
Notes
- Consider using channels to manage connection availability and synchronization.
- Think about how to handle errors during connection creation.
- The
Connectioninterface is intentionally simple to allow for flexibility in connection implementation. You can define aMockConnectionfor testing purposes. - Focus on concurrency safety and graceful shutdown.
- Error handling is important, but the primary focus is on the core connection pool logic.
- You don't need to implement the actual connection logic (e.g., database connection). Just focus on the pool management.
- Consider using a
sync.Mutexto protect shared state within the pool. - Think about how to prevent connection leaks (connections that are acquired but never released).