Hone logo
Hone
Problems

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). The connectionFactory is a function that takes no arguments and returns a Connection interface.
  • Acquire Connection: A method Acquire() should block until a connection is available and then return a Connection interface.
  • 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 Connection interface with a Close() 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 connectionFactory function to create new connections. This allows for flexibility in connection creation.

Expected Behavior:

  • The pool should initially create initialConnections connections.
  • 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 maxConnections is 0?
  • What happens if initialConnections is negative? (Handle gracefully, perhaps by setting it to 0).
  • What happens if the connectionFactory function 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

  • maxConnections must be a non-negative integer.
  • initialConnections must be a non-negative integer.
  • The connectionFactory function must return a value that implements the Connection interface.
  • 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 Connection interface is intentionally simple to allow for flexibility in connection implementation. You can define a MockConnection for 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.Mutex to protect shared state within the pool.
  • Think about how to prevent connection leaks (connections that are acquired but never released).
Loading editor...
go