Connection Pool

Exercise: Connection Pool

Difficulty - Intermediate

Learning Objectives

  • Implement resource pooling pattern
  • Manage connection lifecycle
  • Handle connection health checks
  • Implement pool size limits
  • Coordinate concurrent access

Problem Statement

Create a generic connection pool that manages a fixed number of reusable connections with health checking and timeout support.

Core Components

 1package connpool
 2
 3import (
 4    "context"
 5    "errors"
 6    "sync"
 7    "time"
 8)
 9
10var (
11    ErrPoolClosed   = errors.New("pool is closed")
12    ErrTimeout      = errors.New("timeout acquiring connection")
13    ErrInvalidConn  = errors.New("invalid connection")
14)
15
16type Conn interface {
17    Close() error
18    IsHealthy() bool
19}
20
21type Factory func()
22
23type Pool struct {
24    factory    Factory
25    conns      chan Conn
26    mu         sync.Mutex
27    closed     bool
28    maxSize    int
29    currentSize int
30    idleTimeout time.Duration
31}
32
33func New(factory Factory, maxSize int, idleTimeout time.Duration) *Pool
34func Get(ctx context.Context)
35func Put(conn Conn) error
36func Close() error
37func Stats() PoolStats

Solution

Click to see the solution
  1package connpool
  2
  3import (
  4    "context"
  5    "errors"
  6    "sync"
  7    "time"
  8)
  9
 10var (
 11    ErrPoolClosed  = errors.New("pool is closed")
 12    ErrTimeout     = errors.New("timeout acquiring connection")
 13    ErrInvalidConn = errors.New("invalid connection")
 14)
 15
 16type Conn interface {
 17    Close() error
 18    IsHealthy() bool
 19}
 20
 21type Factory func()
 22
 23type Pool struct {
 24    factory     Factory
 25    conns       chan Conn
 26    mu          sync.Mutex
 27    closed      bool
 28    maxSize     int
 29    currentSize int
 30    idleTimeout time.Duration
 31}
 32
 33type PoolStats struct {
 34    Available int
 35    InUse     int
 36    Total     int
 37}
 38
 39func New(factory Factory, maxSize int, idleTimeout time.Duration) *Pool {
 40    return &Pool{
 41        factory:     factory,
 42        conns:       make(chan Conn, maxSize),
 43        maxSize:     maxSize,
 44        idleTimeout: idleTimeout,
 45    }
 46}
 47
 48func Get(ctx context.Context) {
 49    p.mu.Lock()
 50    if p.closed {
 51        p.mu.Unlock()
 52        return nil, ErrPoolClosed
 53    }
 54    p.mu.Unlock()
 55
 56    select {
 57    case conn := <-p.conns:
 58        if conn.IsHealthy() {
 59            return conn, nil
 60        }
 61        conn.Close()
 62        p.mu.Lock()
 63        p.currentSize--
 64        p.mu.Unlock()
 65        return p.createConn()
 66
 67    case <-ctx.Done():
 68        return nil, ErrTimeout
 69
 70    default:
 71        return p.createConn()
 72    }
 73}
 74
 75func createConn() {
 76    p.mu.Lock()
 77    if p.currentSize >= p.maxSize {
 78        p.mu.Unlock()
 79        return nil, errors.New("pool at max capacity")
 80    }
 81    p.currentSize++
 82    p.mu.Unlock()
 83
 84    conn, err := p.factory()
 85    if err != nil {
 86        p.mu.Lock()
 87        p.currentSize--
 88        p.mu.Unlock()
 89        return nil, err
 90    }
 91
 92    return conn, nil
 93}
 94
 95func Put(conn Conn) error {
 96    p.mu.Lock()
 97    if p.closed {
 98        p.mu.Unlock()
 99        conn.Close()
100        return ErrPoolClosed
101    }
102    p.mu.Unlock()
103
104    if !conn.IsHealthy() {
105        conn.Close()
106        p.mu.Lock()
107        p.currentSize--
108        p.mu.Unlock()
109        return ErrInvalidConn
110    }
111
112    select {
113    case p.conns <- conn:
114        return nil
115    default:
116        conn.Close()
117        p.mu.Lock()
118        p.currentSize--
119        p.mu.Unlock()
120        return nil
121    }
122}
123
124func Close() error {
125    p.mu.Lock()
126    if p.closed {
127        p.mu.Unlock()
128        return nil
129    }
130    p.closed = true
131    p.mu.Unlock()
132
133    close(p.conns)
134
135    for conn := range p.conns {
136        conn.Close()
137    }
138
139    return nil
140}
141
142func Stats() PoolStats {
143    p.mu.Lock()
144    defer p.mu.Unlock()
145
146    available := len(p.conns)
147    return PoolStats{
148        Available: available,
149        InUse:     p.currentSize - available,
150        Total:     p.currentSize,
151    }
152}

Key Takeaways

  • Connection pools reduce overhead of creating connections
  • Health checks prevent using stale connections
  • Max size limits prevent resource exhaustion
  • Timeouts prevent indefinite blocking
  • Proper cleanup prevents resource leaks