Ruby 3 comes with a lot of support tools for concurrency programming, one of them is Fiber Scheduler. In this post, I'll introduce briefly about Fiber Scheduler, the Async gem, and then, implement the simple server using Fiber Scheduler.
What is Fiber
Fiber can be seen as a lightweight thread or thread implemented at the programming language level instead of the OS level.
fiber = Fiber.new do
#...
end
fiber.resume # transfer / Fiber.schedule
A Fiber is a lightweight unit of execution that can be suspended and resumed at specific points. Because only one fiber can execute at a time, they are often referred to as a mechanism for cooperative concurrency. Theoretically, the advantage of Fibers is less context switching compared to Thread, but before Ruby 3, Fibers lacked the scheduler implementation to be useful.
Fiber scheduler
Since Ruby 3, Fibers have been given superpowers in the form of the FiberScheduler.
The Fiber Scheduler consists of two parts:
- Fiber Scheduler interface
- Fiber Scheduler implementation
What Ruby 3 implements is the interface. It would not use the scheduler unless a scheduler implementation is included.
If you want to enable the asynchronous behavior in Ruby, you need to set a Fiber Scheduler object.
Fiber.set_scheduler(scheduler)
The list of Fiber Scheduler implementations and their main differences can be found at Fiber Scheduler List project.
I recommend you read this article to understand more detail about Fiber Scheduler.
Async gem:
One of the most mature and common Fiber Scheduler implementations is by Samuel Williams. Furthermore, he not only implemented a Fiber Scheduler but created the gem called Async which was more powerful for working with asynchronous programming.
If you want to go to details about the Async gem and how to use it, you should read the document Async Guides
The simple use of async gem is quite straightforward, you just need to wrap the computation code in Kernel#Async
method, for example:
require 'async'
Async do |task|
puts "Hello World!"
end
Simple asynchronous HTTP server
In this part, I'll implement the simple HTTP server using Async gem.
For simplicity, I will get the code from Appsignal's article Building a 30 line HTTP server in Ruby. After that, add the async gem to make the code asynchronous.
Using Fiber syntax + Async::Scheduler
require 'socket'
require 'async/scheduler'
Fiber.set_scheduler(Async::Scheduler.new)
server = TCPServer.new 2000 # Server bound to port 2000
app = Proc.new do
['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]]
end
Fiber.schedule do
loop do
session = server.accept # Wait for a client to connect
Fiber.schedule do
status, headers, body = app.call({})
session.print "HTTP/1.1 #{status}\r\n"
headers.each do |key, value|
session.print "#{key}: #{value}\r\n"
end
session.print "\r\n"
body.each do |part|
session.print part
end
session.close
end
end
end
or using Kernel#Async
method
require 'socket'
require 'async'
server = TCPServer.new 2000
app = Proc.new do
['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]]
end
Async do
loop do
session = server.accept # Wait for a client to connect
Async do
status, headers, body = app.call({})
# ... same code as above
end
end
end
Our server is very simple but it is enough to demonstrate the usage and the advantage of Async gem. You can check the full code, and benchmark testing on [this repo].(https://github.com/hungle00/async-http-server)
If you wonder about the Ruby asynchronous server that can be used in production, you can check this gem, it is built on top of the Async gem.