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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A condition variable lets waiting threads sleep until a resource is returned, avoiding busy-loops while holding a lock.
- 2Lazily creating resources up to a cap balances startup cost against a hard limit on concurrent connections.
- 3Wrapping checkout and checkin in an ensure block guarantees resources return to the pool even when the caller raises.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-thread-safe-connection-pool-in-ruby-explained-ruby-ba45/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.