AsyncMagic
AsyncMagic is a lightweight Ruby gem that makes it effortless to add asynchronous method execution to your Ruby classes. With a simple async
declaration, your methods will execute in a managed thread pool, providing non-blocking operations while maintaining clean, readable code.
Inspired by Rails async
methods.
!!! I believe it should be fine to use the gem in production for small apps. For now, I consider this a fun project. Please support with testing it.
Features
- โจ Simple
async
declaration for asynchronous methods - ๐งต Managed thread pool with configurable options
- ๐ Rails integration with ActiveRecord support
- ๐ฏ Support for both instance and class methods
- ๐ Built-in error logging
- โก Non-blocking execution with Future objects
Installation
Add this line to your application's Gemfile:
gem 'async_magic'
$ bundle install
Usage
By default, AsyncMagic will include the methods in all Objects. But you can configure it to include in specific objects (ActiveRecord models, etc.) or manually include it in your classes.
See the Configuration for more details.
class UserHeavyLogic
async
def send_welcome_email(user)
# ... your code
end
async
def self.batch_process(users)
# ... your code
end
end
# or
class UsersController < ApplicationController
def index
# ... your code
log_user_activity
end
private
async
def log_user_activity
# ... your code
end
end
More examples
- check if completed
- get the result
mailer = UserMailer.new
future = mailer.send_welcome_email(user)
# Check if completed
future.completed?
# Get the result (blocks until complete)
result = future.value
- initializer to configure the thread pool
# config/initializers/async_magic.rb
AsyncMagic.configure do |config|
# Include in all ActiveRecord models
config.include_in = :active_record
# Or include globally (available everywhere)
# config.include_in = :global
# Or disable auto-inclusion
# config.include_in = nil
end
- configure the thread pool
AsyncMagic.configure do |config|
config.executor_options = {
min_threads: 10, # Minimum number of threads
max_threads: 50, # Maximum number of threads
auto_terminate: true, # Automatically terminate threads
idletime: 60, # Thread idle time (seconds)
max_queue: 0, # Unlimited queue size
fallback_policy: :caller_runs # What to do when queue is full
}
end
- error handling
class MyService
include AsyncMagic
async
def risky_operation
# If this raises an error, it will be logged
raise "Something went wrong!"
end
end
The error will be logged and the future will contain the error
future = MyService.new.risky_operation
future.rejected? # => true
future.reason # => #<RuntimeError: Something went wrong!>
- manually include AsyncMagic
class DataProcessor
include AsyncMagic
async
def process_data(data)
# Heavy processing here
end
end
processor = DataProcessor.new
future = processor.process_data(large_dataset)
# Wait for completion with timeout
if future.wait(5) # waits up to 5 seconds
result = future.value
else
puts "Processing is taking too long!"
end
AsyncMagic.shutdown # Waits up to 5 seconds for completion
Code style
Just use:
$ bundle exec standardrb
# or
$ bundle exec standardrb --fix
Tasks
- extensive production testing
- more configuration options (maybe different queues?)
- better readme
- consider adding delayed or recurring tasks, as part of "async" macros
Contributing
You are welcome to contribute to this gem.
License
This gem is available as open source under the terms of the MIT License.
Credits
Thanks AI to help me write this gem faster :)