### Start a Reactor and Root Task Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Use the `Async` method to start a reactor and a root task. This example shows sequential execution within the task. ```ruby Async do 1.upto(3) do |i| sleep(i) puts "Hello World #{i}" end end ``` -------------------------------- ### Install Async Gem Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Add the async gem to your project's Gemfile using Bundler. ```bash $ bundle add async ``` -------------------------------- ### Async Scheduler Debug Output Example Source: https://github.com/socketry/async/blob/main/releases.md Example of the detailed scheduler information printed when CONSOLE_LEVEL=debug and the program is interrupted. ```log 0.0s debug: Async::Reactor [oid=0x974] [ec=0x988] [pid=9116] [2024-11-08 14:12:03 +1300] | Scheduler interrupted: Interrupt | # | # | → /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:185:in `transfer' | /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:185:in `block' | /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:207:in `kernel_sleep' | /Users/samuel/Developer/socketry/async/test.rb:7:in `sleep' | /Users/samuel/Developer/socketry/async/test.rb:7:in `sleepy' | /Users/samuel/Developer/socketry/async/test.rb:12:in `block in ' | /Users/samuel/Developer/socketry/async/lib/async/task.rb:197:in `block in run' | /Users/samuel/Developer/socketry/async/lib/async/task.rb:420:in `block in schedule' /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:317:in `select': Interrupt ... (backtrace continues) ... ``` -------------------------------- ### Start Nested Tasks for Concurrency Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Demonstrates starting nested tasks within a loop to achieve concurrent execution. This reduces the total execution time compared to sequential execution. ```ruby Async do 1.upto(3) do |i| Async do sleep(i) puts "Hello World #{i}" end end end ``` -------------------------------- ### Visual Debugging with `async-debug` Source: https://github.com/socketry/async/blob/main/guides/debugging/readme.md Utilize the `async-debug` gem to start a web server for visual inspection of your Async program's state and hierarchy. Access the debugger at `http://localhost:9000`. ```ruby require "async" require "async/debug" Sync do debugger = Async::Debug.serve 3.times do Async do |task| while true duration = rand task.annotate("Sleeping for #{duration} second...") sleep(duration) end end end end ``` -------------------------------- ### Enable and Run Fiber Stall Profiler Source: https://github.com/socketry/async/blob/main/releases.md Example of how to enable the fiber stall profiler by setting the `FIBER_PROFILER_CAPTURE` environment variable and running an async Ruby script. This helps identify event loop stalling issues. ```bash $ FIBER_PROFILER_CAPTURE=true bundle exec ruby -rasync -e 'Async{Fiber.blocking{sleep 0.1}}' ``` -------------------------------- ### Visual Debugging with `async-debug` Gem Source: https://github.com/socketry/async/blob/main/context/debugging.md Utilize the `async-debug` gem to start a web server for visual debugging of Async programs. Open `http://localhost:9000` in your browser to inspect program state and hierarchy. ```ruby require "async" require "async/debug" Sync do debugger = Async::Debug.serve 3.times do Async do |task| while true duration = rand task.annotate("Sleeping for #{duration} second...") sleep(duration) end end end end ``` -------------------------------- ### Async::Barrier Example: Sleep Sort Source: https://github.com/socketry/async/blob/main/lib/async/barrier.md Demonstrates using Async::Barrier to perform a sleep sort. Ensure all tasks are stopped using `barrier.stop` in the `ensure` block. ```ruby require 'async' require 'async/barrier' barrier = Async::Barrier.new Sync do Console.info("Barrier Example: sleep sort.") # Generate an array of 10 numbers: numbers = 10.times.map{rand(10)} sorted = [] # Sleep sort the numbers: numbers.each do |number| barrier.async do |task| sleep(number) sorted << number end end # Wait for all the numbers to be sorted: barrier.wait Console.info("Sorted", sorted) ensure # Ensure all the tasks are stopped when we exit: barrier.stop end ``` -------------------------------- ### Run Benchmark Across All Rubies Source: https://github.com/socketry/async/blob/main/benchmark/rubies/README.md Execute the benchmark script using RVM to test performance across all installed Ruby versions. This is the simplest way to initiate the benchmark. ```bash rvm all do ./benchmark.rb ``` -------------------------------- ### Fetch All URLs with New Parent Scheduler Creation Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Creates a new parent scheduler if one does not already exist for embedded execution. This method simplifies setup when explicit parent control is not required. ```ruby def fetch_all(urls) Sync do |parent| urls.map do |url| parent.async do fetch(url) end end.map(&:wait) end end ``` -------------------------------- ### Async::Queue Close and Error Handling Source: https://github.com/socketry/async/blob/main/releases.md This example shows how to close an `Async::Queue` to prevent further additions and signal waiting tasks. It also demonstrates the `Async::Queue::ClosedError` that is raised when attempting to push to a closed queue and how `dequeue` behaves. ```ruby queue = Async::Queue.new # Start a task waiting for items: waiting_task = Async do queue.dequeue end # Close the queue - this signals the waiting task queue.close # These will raise Async::Queue::ClosedError queue.push(:item) # => raises ClosedError queue.enqueue(:item) # => raises ClosedError queue << :item # => raises ClosedError # Dequeue returns nil when closed and empty queue.dequeue # => nil ``` -------------------------------- ### Aggregating Results with Threads (Unsafe) Source: https://github.com/socketry/async/blob/main/guides/thread-safety/readme.md Shows a basic example of collecting results from multiple threads into an Array. This approach is problematic because there's no guarantee threads have finished when results are accessed. ```ruby done = [] threads = [] 5.times do |i| threads << Thread.new do # Simulate some work sleep(rand(0.1..0.5)) done << i end end # Risk: The threads may not be finished, so `done` is likely incomplete! puts "Done: #{done.inspect}" ``` -------------------------------- ### Task Failure Warning with Console Shims Source: https://github.com/socketry/async/blob/main/releases.md Example output when a task fails with an unhandled exception, using Kernel#warn via the console shims. ```log # Task may have ended with unhandled exception. (irb):4:in `block in ': Boom (RuntimeError) from /home/samuel/Developer/socketry/async/lib/async/task.rb:197:in `block in run' from /home/samuel/Developer/socketry/async/lib/async/task.rb:420:in `block in schedule' ``` -------------------------------- ### Mutex Deadlock Example Source: https://github.com/socketry/async/blob/main/guides/thread-safety/readme.md Demonstrates a potential deadlock scenario when using Mutex within nested synchronized blocks. Avoid holding locks while yielding to user code. ```ruby class Counter def initialize(count = 0) @count = count @mutex = Mutex.new end def increment @mutex.synchronize do @count += 1 end end def times @mutex.synchronize do @count.times do |i| yield i end end end end counter = Counter.new counter.times do |i| counter.increment # deadlock end ``` -------------------------------- ### Mutex Deadlock Example Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Demonstrates a potential deadlock scenario when using Mutex within nested operations. Avoid holding locks during user code execution. ```ruby class Counter def initialize(count = 0) @count = count @mutex = Mutex.new end def increment @mutex.synchronize do @count += 1 end end def times @mutex.synchronize do @count.times do |i| counter.increment # deadlock end end end counter = Counter.new counter.times do |i| counter.increment # deadlock end ``` -------------------------------- ### Transient Task for Time String Cache Source: https://github.com/socketry/async/blob/main/context/tasks.md This example demonstrates using a transient task to maintain a cache of the current time string. The task is an implementation detail and is marked as `transient` to ensure it doesn't keep the reactor alive. ```ruby require "async" require "thread/local" # thread-local gem. class TimeStringCache extend Thread::Local # defines `instance` class method that lazy-creates a separate instance per thread def initialize @current_time_string = nil end def current_time_string refresh! return @current_time_string end private def refresh! @refresh ||= Async(transient: true) do loop do @current_time_string = Time.now.to_s sleep(1) end ensure # When the reactor terminates all tasks, `Async::Cancel` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again: @refresh = nil end end end Async do p TimeStringCache.instance.current_time_string end ``` -------------------------------- ### Transient Task for Time String Cache Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md This example demonstrates using a transient task to periodically update a time string cache. The task is an implementation detail and is marked as `transient: true`. The `ensure` block ensures the task is reset if the reactor terminates. ```ruby require "async" require "thread/local" # thread-local gem. class TimeStringCache extend Thread::Local # defines `instance` class method that lazy-creates a separate instance per thread def initialize @current_time_string = nil end def current_time_string refresh! return @current_time_string end private def refresh! @refresh ||= Async(transient: true) do loop do @current_time_string = Time.now.to_s sleep(1) end ensure # When the reactor terminates all tasks, `Async::Cancel` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again: @refresh = nil end end end Async do p TimeStringCache.instance.current_time_string end ``` -------------------------------- ### Running Async Scheduler Iterations Source: https://github.com/socketry/async/blob/main/context/scheduler.md Shows how to instantiate an Async::Scheduler and run it for a single iteration or until no work remains. Useful for embedding the scheduler within other event loops or for controlled execution. ```ruby require 'async' Console.logger.debug! reactor = Async::Scheduler.new # Run the reactor for 1 second: reactor.async do |task| sleep 1 puts "Finished!" end while reactor.run_once # Round and round we go! end ``` -------------------------------- ### Falcon Performance Benchmark with wrk Source: https://github.com/socketry/async/blob/main/examples/capture/README.md Use wrk to benchmark the Falcon web server. Observe the latency and requests per second. ```shell % wrk -t 8 -c 32 http://localhost:9292/ Running 10s test @ http://localhost:9292/ 8 threads and 32 connections Thread Stats Avg Stdev Max +/- Stdev Latency 106.31ms 10.20ms 211.79ms 98.00% Req/Sec 37.94 5.43 40.00 84.24% 3003 requests in 10.01s, 170.16KB read Requests/sec: 299.98 Transfer/sec: 17.00KB ``` -------------------------------- ### Puma Performance Benchmark with wrk Source: https://github.com/socketry/async/blob/main/examples/capture/README.md Use wrk to benchmark the Puma web server. Observe the latency and requests per second. ```shell wrk -t 8 -c 32 http://localhost:9292/ Running 10s test @ http://localhost:9292/ 8 threads and 32 connections Thread Stats Avg Stdev Max +/- Stdev Latency 108.83ms 3.50ms 146.38ms 86.58% Req/Sec 34.43 6.70 40.00 92.68% 1371 requests in 10.01s, 81.67KB read Requests/sec: 136.94 Transfer/sec: 8.16KB ``` -------------------------------- ### Waiting and Signalling an Async::Condition Source: https://github.com/socketry/async/blob/main/lib/async/condition.md Demonstrates how to create an Async::Condition, have one fiber wait for it, and another fiber signal it with a value. Ensure 'async' and 'console' are required. ```ruby require 'async' Sync do condition = Async::Condition.new Async do Console.info "Waiting for condition..." value = condition.wait Console.info "Condition was signalled: #{value}" end Async do |task| sleep(1) Console.info "Signalling condition..." condition.signal("Hello World") end end ``` -------------------------------- ### Create a Fiber Scheduler Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Manually create an Async::Reactor instance, which is a subclass of Async::Scheduler, and set it as the scheduler for the current Fiber. This allows for explicit control over the event loop and fiber scheduling. ```ruby require 'async/scheduler' scheduler = Async::Scheduler.new Fiber.set_scheduler(scheduler) Fiber.schedule do 1.upto(3) do |i| Fiber.schedule do sleep 1 puts "Hello World" end end end ``` -------------------------------- ### Fiber Scheduler Integration with Async Source: https://github.com/socketry/async/blob/main/context/scheduler.md Demonstrates setting an Async::Scheduler as the global Fiber scheduler to run tasks within the Fiber environment. This allows native Ruby blocking operations to be managed by the Async scheduler. ```ruby require "async" scheduler = Async::Scheduler.new Fiber.set_scheduler(scheduler) Fiber.schedule do puts "Hello World!" end ``` -------------------------------- ### Concurrent HTTP Requests with Async Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Demonstrates performing concurrent HTTP requests using Net::HTTP within an Async block. Ensure that Net::HTTP is compatible with the fiber scheduler for optimal performance. ```ruby urls = ["http://example.com", "http://example.org", "http://example.net"] Async do # Perform several concurrent requests: responses = urls.map do |url| Async do Net::HTTP.get(URI(url)) end end.map(&:wait) end ``` -------------------------------- ### Optimistic vs. Pessimistic Scheduling Behavior Source: https://github.com/socketry/async/blob/main/context/scheduler.md Illustrates the difference between optimistic and pessimistic scheduling strategies in Async. An optimistic scheduler executes tasks immediately, while a pessimistic one queues them for the next event loop iteration. Avoid relying on execution order. ```ruby Async do puts "Hello " Async do puts "World" end puts "!" end ``` -------------------------------- ### Thread-Safe Connection Pool (Problematic) Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Illustrates a thread-safe connection pool implementation using a Mutex. However, relying on `Thread.current` as a key can still be problematic in fiber-based applications. ```ruby class Pool def initialize @connections = {} @mutex = Mutex.new end def current_connection @mutex.synchronize do @connections[Thread.current] ||= create_new_connection end end end ``` -------------------------------- ### Nested Task Execution with Async Scheduler Source: https://github.com/socketry/async/blob/main/context/scheduler.md Demonstrates creating nested asynchronous tasks, annotating their progress, and managing their lifecycle, including cancellation. Use this to structure complex concurrent operations. ```ruby require 'async' def sleepy(duration, task: Async::Task.current) task.async do |subtask| subtask.annotate "I'm going to sleep #{duration}s..." sleep duration puts "I'm done sleeping!" end end def nested_sleepy(task: Async::Task.current) task.async do |subtask| subtask.annotate "Invoking sleepy 5 times..." 5.times do |index| sleepy(index, task: subtask) end end end Async do |task| task.annotate "Invoking nested_sleepy..." subtask = nested_sleepy # Print out all running tasks in a tree: task.print_hierarchy($stderr) # Kill the subtask subtask.cancel end ``` -------------------------------- ### Ruby 3.2.1 Thread Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Provides Thread creation benchmark results for Ruby 3.2.1, with an average of 11.77us and a best of 8.94us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby thread-creation.rb 3.2.1 Thread duration: 13.3us Thread duration: 11.71us Thread duration: 13.17us Thread duration: 10.61us Thread duration: 8.94us Thread duration: 11.99us Thread duration: 12.63us Thread duration: 12.04us Thread duration: 10.63us Thread duration: 12.73us Average: 11.77us Best: 8.94us ``` -------------------------------- ### Basic Async::Promise Usage Source: https://github.com/socketry/async/blob/main/releases.md Demonstrates the fundamental usage of Async::Promise for asynchronous operations. Promises can be resolved in one thread and awaited in another, bridging Thread and Fiber concurrency models. ```ruby require "async/promise" # Basic promise usage - works independently of Async framework promise = Async::Promise.new # In another thread or fiber, resolve the promise Thread.new do sleep(1) # Simulate some work promise.resolve("Hello, World!") end # Wait for the result result = promise.wait puts result # => "Hello, World!" # Check promise state puts promise.resolved? # => true puts promise.completed? # => true ``` -------------------------------- ### Ruby 3.1.3 Thread Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Presents Thread creation benchmark results for Ruby 3.1.3, with an average of 11.68us and a best of 9.34us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby thread-creation.rb 3.1.3 Thread duration: 13.42us Thread duration: 11.78us Thread duration: 11.84us Thread duration: 10.92us Thread duration: 10.98us Thread duration: 9.34us Thread duration: 11.2us Thread duration: 11.29us Thread duration: 14.21us Thread duration: 11.86us Average: 11.68us Best: 9.34us ``` -------------------------------- ### Ruby 3.2.1 Fiber Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Displays Fiber creation benchmark results for Ruby 3.2.1, averaging 0.86us with a best of 0.81us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby fiber-creation.rb 3.2.1 Fiber duration: 1.07us Fiber duration: 0.91us Fiber duration: 0.83us Fiber duration: 0.86us Fiber duration: 0.81us Fiber duration: 0.83us Fiber duration: 0.82us Fiber duration: 0.83us Fiber duration: 0.84us Fiber duration: 0.81us Average: 0.86us Best: 0.81us ``` -------------------------------- ### Flexible Timeouts with Async::Scheduler Source: https://github.com/socketry/async/blob/main/releases.md Demonstrates how to use `Async::Scheduler#with_timeout` with a block to dynamically adjust or cancel timeouts during execution. Useful for long-running tasks where timeout needs may change. ```ruby Async do Async::Scheduler.with_timeout(5) do |timeout| # Do some work that may take a while... if some_condition timeout.cancel! # Cancel the timeout else # Add 10 seconds to the current timeout: timeout.adjust(10) # Reduce the timeout by 10 seconds: timeout.adjust(-10) # Set the timeout to 10 seconds from now: timeout.duration = 10 # Increase the current duration: timeout.duration += 10 end end end ``` -------------------------------- ### Ruby 3.0.4 Thread Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Details Thread creation benchmark results for Ruby 3.0.4, with an average of 10.03us and a best of 9.28us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby thread-creation.rb 3.0.4 Thread duration: 13.72us Thread duration: 10.92us Thread duration: 9.97us Thread duration: 9.44us Thread duration: 9.28us Thread duration: 9.38us Thread duration: 9.31us Thread duration: 9.45us Thread duration: 9.4us Thread duration: 9.39us Average: 10.03us Best: 9.28us ``` -------------------------------- ### Ruby 3.1.3 Fiber Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Shows Fiber creation benchmark results for Ruby 3.1.3, averaging 0.82us with a best of 0.71us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby fiber-creation.rb 3.1.3 Fiber duration: 0.94us Fiber duration: 0.89us Fiber duration: 0.86us Fiber duration: 0.87us Fiber duration: 0.71us Fiber duration: 0.78us Fiber duration: 0.79us Fiber duration: 0.91us Fiber duration: 0.75us Fiber duration: 0.75us Average: 0.82us Best: 0.71us ``` -------------------------------- ### Async::PriorityQueue with Consumers Source: https://github.com/socketry/async/blob/main/releases.md Illustrates the use of Async::PriorityQueue for managing tasks with different priorities. Consumers with higher priority values are served first, while maintaining FIFO order for equal priorities. Requires the 'async' and 'async/priority_queue' gems. ```ruby require "async" require "async/priority_queue" Async do queue = Async::PriorityQueue.new # Start consumers with different priorities low_priority = async do puts "Low priority consumer got: #{queue.dequeue(priority: 1)}" end medium_priority = async do puts "Medium priority consumer got: #{queue.dequeue(priority: 5)}" end high_priority = async do puts "High priority consumer got: #{queue.dequeue(priority: 10)}" end # Add items to the queue queue.push("first item") queue.push("second item") queue.push("third item") # Output: # High priority consumer got: first item # Medium priority consumer got: second item # Low priority consumer got: third item end ``` -------------------------------- ### Resource Management with Ensure Block Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Wrap resource acquisition and usage within `begin...ensure` blocks to guarantee cleanup, such as closing sockets. This pattern ensures resources are released even if `Async::Cancel` exceptions occur. ```ruby Async do begin socket = connect(remote_address) # May raise Async::Cancel socket.write(...) # May raise Async::Cancel socket.read(...) # May raise Async::Cancel ensure socket.close if socket end end ``` -------------------------------- ### Ruby 3.0.4 Fiber Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Presents Fiber creation benchmark results for Ruby 3.0.4, showing an average of 0.83us and a best of 0.71us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby fiber-creation.rb 3.0.4 Fiber duration: 0.88us Fiber duration: 0.85us Fiber duration: 0.78us Fiber duration: 0.76us Fiber duration: 0.81us Fiber duration: 0.71us Fiber duration: 0.87us Fiber duration: 0.89us Fiber duration: 0.76us Fiber duration: 0.96us Average: 0.83us Best: 0.71us ``` -------------------------------- ### Create an Asynchronous Task Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Use the Kernel#Async method to create a new asynchronous task. This method is available globally and captures sequential computations within a Fiber. Blocking operations within the task yield control, allowing other fibers to execute. ```ruby require 'async' Async do |task| puts "Hello World!" end ``` -------------------------------- ### Wait for First N Tasks with Async::Waiter and Async::Barrier Source: https://github.com/socketry/async/blob/main/context/tasks.md Combine Async::Waiter and Async::Barrier to wait for a specific number of tasks to complete, then cancel the rest. ```ruby Async do barrier = Async::Barrier.new begin jobs.each do |job| barrier.async do # ... process job ... end end # Wait for the first two jobs to complete: done = [] barrier.wait do |task| done << task.wait # If you don't want to wait for any more tasks you can break: break if done.size >= 2 end ensure # The remainder of the tasks will be cancelled: barrier.cancel end end ``` -------------------------------- ### Per-Request State using Thread.current[key] (Discouraged) Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Shows the use of `Thread.current[key]` for per-request state. While technically functional, it suffers from readability issues and is generally discouraged in favor of `Fiber.attr` or `Fiber[key]`. ```ruby Thread.current[:connection] ||= create_new_connection ``` -------------------------------- ### Puma Process Statistics with strace Source: https://github.com/socketry/async/blob/main/examples/capture/README.md Use strace to capture system call information for a running Puma process. Analyze recvfrom and sendto calls. ```shell 0.0s: Process 28448 start times: | # ^C24.89s: strace -p 28448 | ["recvfrom", {:"% time"=>64.65, :seconds=>0.595275, :"usecs/call"=>13, :calls=>44476, :errors=>27769, :syscall=>"recvfrom"}] | ["sendto", {:"% time"=>30.68, :seconds=>0.282467, :"usecs/call"=>18, :calls=>15288, :errors=>nil, :syscall=>"sendto"}] | ["write", {:"% time"=>4.66, :seconds=>0.042921, :"usecs/call"=>15, :calls=>2772, :errors=>nil, :syscall=>"write"}] | ["read", {:"% time"=>0.02, :seconds=>0.000157, :"usecs/call"=>8, :calls=>19, :errors=>1, :syscall=>"read"}] | [:total, {:"% time"=>100.0, :seconds=>0.92082, :"usecs/call"=>nil, :calls=>62555, :errors=>27770, :syscall=>"total"}] 24.89s: Process 28448 end times: | # 24.89s: Process Waiting: 0.9208s out of 2.56s | Wait percentage: 35.97% ``` -------------------------------- ### Concurrent Map-Reduce with Async Tasks Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Illustrates a concurrent map-reduce pattern using nested `Async` blocks to fetch data concurrently and then aggregate the results. ```ruby Async do # Map (create several concurrent tasks) users_size = Async{User.size} posts_size = Async{Post.size} # Reduce (wait for and merge the results) average = posts_size.wait / users_size.wait puts "#{users_size.wait} users created #{average} posts on average." end ``` -------------------------------- ### Falcon Process Statistics with strace Source: https://github.com/socketry/async/blob/main/examples/capture/README.md Use strace to capture system call information for a running Falcon process. Analyze sendto and recvfrom calls. ```shell 0.0s: Process 28065 start times: | # ^C15.11s: strace -p 28065 | ["sendto", {:"% time"=>57.34, :seconds=>0.595047, :"usecs/call"=>14, :calls=>39716, :errors=>32, :syscall=>"sendto"}] | ["recvfrom", {:"% time"=>42.58, :seconds=>0.441867, :"usecs/call"=>12, :calls=>36718, :errors=>70, :syscall=>"recvfrom"}] | ["read", {:"% time"=>0.07, :seconds=>0.000723, :"usecs/call"=>7, :calls=>98, :errors=>nil, :syscall=>"read"}] | ["write", {:"% time"=>0.01, :seconds=>0.000112, :"usecs/call"=>56, :calls=>2, :errors=>nil, :syscall=>"write"}] | [:total, {:"% time"=>100.0, :seconds=>1.037749, :"usecs/call"=>nil, :calls=>76534, :errors=>102, :syscall=>"total"}] 15.11s: Process 28065 end times: | # 15.11s: Process Waiting: 1.0377s out of 1.55s | Wait percentage: 66.95% ``` -------------------------------- ### Thread-Local Storage for Per-Request State (Problematic) Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Demonstrates a common but problematic approach to per-request state using `Thread.current.thread_variable_get`. This can lead to data corruption when fibers share the same thread. ```ruby class RequestContext def self.current Thread.current.thread_variable_get(:request_context) || Thread.current.thread_variable_set(:request_context, Hash.new) end end ``` -------------------------------- ### Debugging with `puts` in Async Source: https://github.com/socketry/async/blob/main/guides/debugging/readme.md Use `puts` within `Fiber.blocking{}` to print messages sequentially and avoid interleaved output in asynchronous programs. Do not use `Fiber.blocking{}` in production. ```ruby require "async" Async do 3.times do |i| sleep i Fiber.blocking{puts "Slept for #{i} seconds."} end end ``` -------------------------------- ### Use Kernel::Barrier for Concurrent Task Management in Ruby Source: https://github.com/socketry/async/blob/main/releases.md Introduces Kernel::Barrier for easily managing multiple concurrent tasks. It encapsulates creating an Async::Barrier, yielding it to a block, and ensuring all tasks complete or are stopped upon exit. Exceptions raised by tasks are propagated. ```ruby require "async" Barrier do |barrier| 3.times do |i| barrier.async do |task| sleep(rand * 0.1) # Simulate work puts "Task #{i} completed" end end end # All tasks are guaranteed to complete or be stopped when the block exits. ``` -------------------------------- ### Creating Periodic Timers with End-to-Start Delay Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md For recurring work where each iteration should be followed by a delay, simply use `sleep` between loop iterations. This ensures the process takes a break and doesn't spend all its time on the work itself. ```ruby require 'async' period = 30 Async do |task| loop do puts Time.now # ... process job ... sleep(period) end end ``` -------------------------------- ### Add fiber-profiler gem Source: https://github.com/socketry/async/blob/main/releases.md Command to add the `fiber-profiler` gem to your project's Gemfile using Bundler. ```bash $ bundle add fiber-profiler ``` -------------------------------- ### Creating Periodic Timers with Start-to-Start Delay Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md To achieve a periodic timer that runs on a start-to-start schedule, track the next run time using the monotonic clock and sleep for the remaining duration. ```ruby require 'async' period = 30 Async do |task| run_next = Async::Clock.now loop do run_next += period puts Time.now # ... process job ... if (remaining = run_next - Async::Clock.now) > 0 sleep(remaining) end end end ``` -------------------------------- ### Actual Thread-Local Storage with Thread.attr Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Demonstrates how to use `Thread.attr` for true per-thread storage, which is not inherited by child fibers. This is useful for state or caches that persist across fibers but require thread-level isolation. ```ruby Thread.attr :connection_pool # Each thread gets its own connection pool Thread.current.connection_pool = ConnectionPool.new(size: 5) def get_connection Thread.current.connection_pool.checkout end ``` -------------------------------- ### Unsafe Lazy Initialization with Mutex Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Demonstrates an unsafe lazy initialization pattern where the Mutex is initialized lazily. This can lead to race conditions if multiple threads access the Mutex before it's initialized. ```ruby class Loader def self.data @mutex ||= Mutex.new # Issue: Not thread-safe @mutex.synchronize do # Double-checked locking pattern: return @data if @data # Now we are sure that @data is nil, we can safely fetch it: @data = JSON.load_file("data.json") end return @data end end ``` -------------------------------- ### Idler for Adaptive Concurrency Source: https://github.com/socketry/async/blob/main/guides/best-practices/readme.md Use `Async::Idler` to schedule work based on processor utilization, aiming for a specific saturation level (e.g., 80%). The default `Barrier` uses an idler. ```ruby Barrier do |barrier| # Uses Async::Idler.new(0.8) by default work.each do |work| barrier.async do work.call end end end ``` ```ruby Async do # Create an idler that will aim for a load average of 80%: idler = Async::Idler.new(0.8) # Some list of work to be done: work.each do |work| idler.async do # Do the work: work.call end end end ``` -------------------------------- ### Synchronous Execution with Sync Source: https://github.com/socketry/async/blob/main/guides/best-practices/readme.md Use `Sync` to run code within an event loop without creating a new task. It's more efficient than `Async{}.wait` for top-level execution. ```ruby require "async" class Packages def initialize(urls) @urls = urls end def fetch # A common use case is to make functions which appear synchronous, but internally use asynchronous execution: Sync do |task| @urls.map do |url| task.async do fetch(url) end end.map(&:wait) end end end ``` -------------------------------- ### Synchronous Execution with Sync Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Use the Sync method for code that uses asynchronous primitives but doesn't require explicit asynchronous execution relative to other tasks. It reuses an existing event loop if available, or creates one if not, offering efficiency over Async{...}.wait. ```ruby require "async/http/internet" def fetch(url) Sync do internet = Async::HTTP::Internet.new return internet.get(url).read end end # At the level of your program, this method will create an event loop: fetch("https://example.com") Sync do # The event loop already exists, and will be reused: fetch("https://example.com") end ``` -------------------------------- ### Create a Transient Task Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Specify `transient: true` when creating a task to make it transient. This task will not keep the reactor alive and will be cancelled when other non-transient tasks finish. ```ruby @pruner = Async(transient: true) do loop do sleep(1) prune_connection_pool end end ``` -------------------------------- ### Fetch All URLs with Parent Task Injection Source: https://github.com/socketry/async/blob/main/guides/getting-started/readme.md Injects a parent task into the fetch_all method to manage embedded execution. Useful for scenarios requiring explicit control over the parent scheduler, such as barriers or semaphores. ```ruby def fetch_all(urls, parent: Async::Task.current) urls.map do |url| parent.async do fetch(url) end end.map(&:wait) end ``` -------------------------------- ### Create a Transient Task Source: https://github.com/socketry/async/blob/main/context/tasks.md Use the `transient: true` option when creating a task to make it transient. Transient tasks are not considered by `finished?` and are cancelled when all non-transient tasks complete. ```ruby @pruner = Async(transient: true) do loop do sleep(1) prune_connection_pool end end ``` -------------------------------- ### Inheritable Per-Request State with Fiber[key] Source: https://github.com/socketry/async/blob/main/guides/thread-safety/readme.md Demonstrates using `Fiber[key]` to store per-request state that is inherited by child fibers and threads. This is useful for context propagation like user IDs or trace IDs. ```ruby Fiber[:user_id] = request.session[:user_id] Fiber[:trace_id] = request.headers["X-Trace-ID"] jobs.each do |job| Thread.new do # Child threads inherit the fiber storage: puts "Processing job for user #{Fiber[:user_id]} (trace: #{Fiber[:trace_id]})"; process_job(job) end end Async do |task| # Child fibers also inherit the storage: task.async do puts "Background task for user: #{Fiber[:user_id]}" end end ``` -------------------------------- ### Wait for First N Tasks with Async Barrier and Waiter Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Combine Async::Barrier and Async::Waiter to wait for a specific number of tasks to complete. The barrier's `wait` method can accept a block to process completed tasks and break early. Remember to cancel remaining tasks in a `ensure` block. ```ruby Async do barrier = Async::Barrier.new begin jobs.each do |job| barrier.async do # ... process job ... end end # Wait for the first two jobs to complete: done = [] barrier.wait do |task| done << task.wait # If you don't want to wait for any more tasks you can break: break if done.size >= 2 end ensure # The remainder of the tasks will be cancelled: barrier.cancel end end ``` -------------------------------- ### Ruby 2.7.7 Thread Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Benchmarks Thread creation in Ruby 2.7.7, with an average duration of 10.48us and a best of 9.22us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby thread-creation.rb 2.7.7 Thread duration: 13.44us Thread duration: 11.32us Thread duration: 11.05us Thread duration: 10.71us Thread duration: 9.22us Thread duration: 9.89us Thread duration: 9.48us Thread duration: 10.25us Thread duration: 9.92us Thread duration: 9.52us Average: 10.48us Best: 9.22us ``` -------------------------------- ### Wait for All Tasks with Async Barrier Source: https://github.com/socketry/async/blob/main/guides/tasks/readme.md Employ Async::Barrier to manage a group of tasks and wait for all of them to complete. This is useful for ensuring all background operations finish before proceeding. ```ruby barrier = Async::Barrier.new Async do jobs.each do |job| barrier.async do # ... process job ... end end # Wait for all jobs to complete: barrier.wait end ``` -------------------------------- ### Async::Barrier Wait for Partial Task Completion Source: https://github.com/socketry/async/blob/main/releases.md This snippet demonstrates how to use `barrier.wait` with a block to stop waiting after a specific number of tasks have completed. It's useful for scenarios where only a subset of task results is needed. ```ruby # Wait for only the first 3 tasks to complete count = 0 barrier.wait do |task| task.wait count += 1 break if count >= 3 end ``` -------------------------------- ### Enable Worker Pool for Fiber Scheduler Source: https://github.com/socketry/async/blob/main/releases.md Set this environment variable to enable the Async scheduler's worker pool for general blocking operations. Note that this may introduce overhead and should be benchmarked. ```bash ASYNC_SCHEDULER_WORKER_POOL=true ``` -------------------------------- ### Ruby 2.6.10 Thread Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Measures the duration of Thread creation in Ruby 2.6.10. Results show an average duration of 48.94us with a best of 44.85us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby thread-creation.rb 2.6.10 Thread duration: 47.01us Thread duration: 44.85us Thread duration: 50.91us Thread duration: 45.06us Thread duration: 47.04us Thread duration: 48.75us Thread duration: 52.04us Thread duration: 54.62us Thread duration: 48.35us Thread duration: 50.79us Average: 48.94us Best: 44.85us ``` -------------------------------- ### Ruby 2.6.10 Fiber Creation Benchmark Source: https://github.com/socketry/async/blob/main/benchmark/core/results.md Measures the duration of Fiber creation in Ruby 2.6.10. Results indicate an average duration of 2.23us with a best of 1.96us. ```shell samuel@aiko ~/P/s/a/b/core (main)> ruby fiber-creation.rb 2.6.10 Fiber duration: 3.34us Fiber duration: 2.57us Fiber duration: 2.06us Fiber duration: 2.23us Fiber duration: 1.96us Fiber duration: 2.13us Fiber duration: 1.99us Fiber duration: 2.13us Fiber duration: 1.97us Fiber duration: 1.98us Average: 2.23us Best: 1.96us ``` -------------------------------- ### Thread-Safe State Management with Mutex and ConditionVariable Source: https://github.com/socketry/async/blob/main/context/thread-safety.md Use Mutex and ConditionVariable to atomically check and modify shared resource state. This ensures that state transitions are handled safely in concurrent environments. ```ruby class System def initialize @mutex = Mutex.new @condition = ConditionVariable.new @usage = 0 end def release @mutex.synchronize do @usage -= 1 @condition.signal if @usage == 0 end end def wait_until_free @mutex.synchronize do while @usage > 0 @condition.wait(@mutex) end end end ``` -------------------------------- ### Use Async::LimitedQueue for Back-Pressure Source: https://github.com/socketry/async/blob/main/guides/best-practices/readme.md Employ Async::LimitedQueue to prevent unbounded memory use by buffering a fixed number of items. Pushing to a full queue will block. ```ruby Async do |task| queue = Async::LimitedQueue.new(8) # Everything else is the same from the queue example, except that the pushing onto the queue will block once 8 items are buffered. end ```