ruby 59 lines · 8 steps

Building a thread-safe connection pool in Ruby

A bounded pool that reuses connections, lazily creates them up to a cap, and blocks callers until one frees up or a deadline passes.

Explained by highlit
1require "thread"
2 
3class ConnectionPool
4 class TimeoutError < StandardError; end
5 
6 def initialize(size: 5, timeout: 5, &factory)
7 raise ArgumentError, "block required to build connections" unless factory
8 
9 @factory = factory
10 @timeout = timeout
11 @mutex = Mutex.new
12 @resource = ConditionVariable.new
13 @available = Queue.new
14 @created = 0
15 @max = size
16 end
17 
18 def with
19 conn = checkout
20 begin
21 yield conn
22 ensure
23 checkin(conn)
24 end
25 end
26 
27 def checkout
28 deadline = clock + @timeout
29 
30 @mutex.synchronize do
31 loop do
32 return @available.pop(true) unless @available.empty?
33 
34 if @created < @max
35 @created += 1
36 return @factory.call
37 end
38 
39 remaining = deadline - clock
40 raise TimeoutError, "waited #{@timeout}s for a connection" if remaining <= 0
41 
42 @resource.wait(@mutex, remaining)
43 end
44 end
45 end
46 
47 def checkin(conn)
48 @mutex.synchronize do
49 @available.push(conn)
50 @resource.signal
51 end
52 end
53 
54 private
55 
56 def clock
57 Process.clock_gettime(Process::CLOCK_MONOTONIC)
58 end
59end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A condition variable lets waiting threads sleep until a resource is returned, avoiding busy-loops while holding a lock.
  2. 2Lazily creating resources up to a cap balances startup cost against a hard limit on concurrent connections.
  3. 3Wrapping checkout and checkin in an ensure block guarantees resources return to the pool even when the caller raises.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a thread-safe connection pool in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code