My first concurrent
app in Crystal
Lorenzo Barasti
My first concurrent
app in Crystal
1. Real-world problem
2. Abstract problem
3. CSP and Crystal
4. Putting things together
Intro
"max size: 1MB"
Intro - "max size: 1MB"
Characters
- a bulky PDF
- a form that won't accept it
- the internet (chorus)
"Surely, someone has solved this before"
True
False
True
"There must be a web app for that"
True
False
False
"There must be an open source tool for that"
True
False
True
Part 1
"Hold my ginger beer"
An engineer
"It's as simple as that"
API
gs
Data flow
Request handlers
Compressors
Publisher
Data flow
Output
Producers
Processors
Collector
A pipeline, indeed!
Part 2
Communicating Sequential
Processes
CSP
A concurrent paradigm where
Processes communicate
by passing messages
over channels
CSP in Crystal
A concurrent paradigm where
Processes Fibers communicate
by passing messages
over Channels
CSP in Crystal
A concurrent paradigm where
Processes Fibers communicate
by passing messages
over channels
Fibers
Lightweight
Cooperative
Unit of execution
CSP in Crystal
A concurrent paradigm where
Processes Fibers communicate
by passing messages
over Channels
Channels
blocking #send and #receive
similar to queues
Regulate data exchange between fibers
Fibers
Channel
Message
CSP in Crystal
spawn do
# your concurrent task!
end
To start a Fiber we
Â
CSP in Crystal
ch = Channel(Job).new
Creating a Channel
Â
Sending & receiving
Â
ch.send(Msg.new(uuid))
msg = ch.receive
Would you like to see some code?
Yes
No
Yes
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
X
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
ch.send msg
puts ch.receive
X
CSP in Crystal
When a Fiber blocks, it yields control to another Fiber.
Â
Â
Fiber.yield
sleep 2
HTTP.get(url)
ch.receive
ch.send obj
Crystal's execution model
- Scheduler: allocates time for fibers to run
- Event loop: a fiber that coordinates async tasks
- Garbage collector: clears unused memory - runs on a separate thread
Crystal's execution model
fiber A
Thread
Scheduler
Crystal's execution model
Thread
Scheduler
fiber A
fiber B
Crystal's execution model
Scheduler
Thread
fiber A
fiber B
spawn(name: "fiber A") do
spawn(name: "fiber Z") do
# do something
end
msg = ch.receive
end
fiber Z
Crystal's execution model
Scheduler
Thread
fiber A
fiber B
spawn(name: "fiber A") do
spawn(name: "fiber Z") do
# do something
end
msg = ch.receive
end
fiber Z
Crystal's execution model
Scheduler
Thread
fiber A
Thread
fiber B
spawn(name: "fiber A") do
spawn(name: "fiber Z") do
# do something
end
msg = ch.receive
end
fiber Z
Crystal's execution model
Scheduler
Thread
fiber A
Thread
fiber B
fiber Z
Crystal's execution model
Scheduler
Thread
Thread
fiber B
fiber A
fiber Z
Crystal's execution model
Scheduler
Thread
fiber A
Thread
fiber B
fiber C
fiber Z
Crystal's execution model
Scheduler
Thread
Thread
fiber C
fiber A
fiber B
spawn(name: "fiber B") do
Fiber.yield
end
fiber Z
Crystal's execution model
Scheduler
Thread
Thread
fiber C
fiber A
fiber B
fiber Z
Crystal's execution model
Scheduler
Thread
Thread
fiber C
fiber A
fiber B
fiber Z
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
X
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
X
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
X
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
CSP in Crystal
ch = Channel(Job).new
msg = Msg.new(uuid)
spawn(name: "processor") do
puts ch.receive
end
ch.send msg
Termination
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
CSP in Crystal
ch = Channel(Job).new
2.times.each { |i|
spawn(name: "processor_#{i}") do
loop do
puts "#{Fiber.current.name}: #{ch.receive}"
end
end
}
loop do
ch.send Msg.new(UUID.random)
sleep 0.5
end
X
X
Interlude
Pipelines everywhere
Pipelines everywhere
Pipelines everywhere
To share state we communicate via channels
Â
Job_id_stream = Channel(Int32).new
spawn(name: "job_id_generator") do
i = 0
loop do
Job_id_stream.send i
i += 1
end
end
def handle_request(request)
job_id = Job_id_stream.receive
# ...
end
Pipelines everywhere
To share state we communicate via channels
Â
Job_id_stream = Channel(Int32).new
spawn(name: "job_id_generator") do
i = 0
loop do
Job_id_stream.send i
i += 1
end
end
def handle_request(request)
job_id = Job_id_stream.receive
# ...
end
Pipelines everywhere
To share state we communicate via channels
Â
Job_id_stream = Channel(Int32).new
spawn(name: "job_id_generator") do
i = 0
loop do
Job_id_stream.send i
i += 1
end
end
def handle_request(request)
job_id = Job_id_stream.receive
# ...
end
Pipelines everywhere
To share state we communicate via channels
Â
Job_id_stream = Channel(Int32).new
spawn(name: "job_id_generator") do
i = 0
loop do
Job_id_stream.send i
i += 1
end
end
def handle_request(request)
job_id = Job_id_stream.receive
# ...
end
Pipelines everywhere
Job_stream = Channel(Job).new
def handle_request(request)
params = extract_params(request)
job_id = Job_id_stream.receive
persist(job_id, params.file)
job = Job.new(job_id, params.options)
Job_stream.send(job)
end
WORKERS.each {
spawn do
loop do
process(Job_stream.receive)
end
end
}
Pipelines everywhere
Job_stream = Channel(Job).new
def handle_request(request)
params = extract_params(request)
job_id = Job_id_stream.receive
persist(job_id, params.file)
job = Job.new(job_id, params.options)
Job_stream.send(job)
end
WORKERS.each {
spawn do
loop do
process(Job_stream.receive)
end
end
}
Pipelines everywhere
Job_stream = Channel(Job).new
def handle_request(request)
params = extract_params(request)
job_id = Job_id_stream.receive
persist(job_id, params.file)
job = Job.new(job_id, params.options)
Job_stream.send(job)
end
WORKERS.each {
spawn do
loop do
process(Job_stream.receive)
end
end
}
Pipelines everywhere
Part 3
Wrapping up!
Wrapping up
Fibers
Channels
Success!
{ name: lorenzo.barasti, live_codes_on: twitch.com/lbarasti, blogs_at: lbarasti.com, tweets_as: @lbarasti }
My first concurrent
app in Crystal
Thanks for listening!
My first concurrent
app in Crystal
My first concurrent app in Crystal
By Lorenzo Barasti
My first concurrent app in Crystal
We look into Crystal's concurrency model (CSP) and how that shapes the way we write concurrent applications
- 868