Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
99: Fibers Revisited In Depth
We've already touched on Fibers as a way to pause and resume execution, but most developers stop there. They treat Fibers like a fancy version of a generator or a way to implement a cooperative state machine. If that's all you're doing, you're barely scratching the surface. The real power of Fibers isn't in the manual resume and yield calls—it's in how they enable non-blocking I/O without turning your codebase into a nested mess of callbacks.
The Manual Orchestration Trap
When I first started using Fibers, I did what most people do: I tried to be the conductor. I'd wrap a few I/O tasks in Fibers and write a while loop that manually called resume on each one until they all finished. It felt clever because I was avoiding threads, but I quickly realized I had just rebuilt a very poor version of an event loop.
# The "I'll manage it myself" approach
fibers = urls.map do |url|
Fiber.new do
# Imagine this is a blocking network call
result = Net::HTTP.get(URI(url))
Fiber.yield result
end
end
results = []
while fibers.any?(&:alive?)
fibers.each do |f|
results << f.resume if f.alive?
end
end
Here is the problem: Net::HTTP.get is a blocking call. Even though it's inside a Fiber, it blocks the entire Ruby thread. The Fiber doesn't magically make the network call asynchronous; it just gives you a place to pause. In the example above, the loop doesn't actually provide concurrency. It just executes the requests sequentially, pausing and resuming them in a way that adds overhead without any of the performance gains. You're paying the "complexity tax" of Fibers without getting the "speed dividend."
Letting the Scheduler Handle the Wait
The breakthrough came in Ruby 3.0 with the Fiber Scheduler. Instead of you manually deciding when to yield and resume, Ruby can now hook into the I/O operations themselves. When a Fiber hits a blocking operation (like reading from a socket), it automatically yields control back to the scheduler, which then resumes another Fiber that is actually ready to do work.
This is a fundamental shift in responsibility. You stop worrying about the mechanics of the pause and start focusing on the logic of the concurrency. I usually recommend using a gem like async to provide the scheduler implementation, because writing a production-ready scheduler from scratch is a rabbit hole you probably don't want to fall down.
require 'async'
require 'net/http'
Async do
urls.each do |url|
Async do
# This looks blocking, but the scheduler intercepts it!
response = Net::HTTP.get(URI(url))
puts "Fetched #{url}: #{response.size} bytes"
end
end
end
The Trade-off: Determinism vs. Magic
Now, you might ask why we wouldn't just do this every time. The trade-off is transparency. When you're manually resuming Fibers, you have absolute, deterministic control over the execution order. You know exactly who runs and when. With a scheduler, you're introducing a layer of "magic." The runtime decides the most efficient way to switch contexts based on which socket becomes readable first.
For 99% of I/O-bound applications—web scrapers, API aggregators, or chat servers—the scheduler is the correct choice. It removes the boilerplate and prevents the "blocking the world" bug that plagues naive Fiber implementations. You get the performance of an event loop (like Node.js) but you get to write your code in a straight, synchronous line. That's the real win here: avoiding the "callback hell" while keeping the efficiency of non-blocking I/O.
📋 Practical Task
Building a Non-Blocking API Health Monitor
Your goal is to create a script that checks the health of multiple external APIs simultaneously. You must avoid the "Manual Orchestration" pattern and instead use a Fiber-based scheduler to ensure that one slow API doesn't block the checks for the others.
Requirements:
- Use the
asyncgem (orFiber.set_schedulerif you are implementing a custom one) to wrap your requests. - Create a list of at least five different URLs (you can use
https://httpbin.org/delay/1,/delay/2, etc., to simulate slow responses). - The script should initiate all requests concurrently.
- Each Fiber should print the URL and the time it took to complete the request.
- Verify that the total execution time of the script is roughly equal to the slowest single request, rather than the sum of all requests.
There are no comments for now.